<!DOCTYPE html>
<html lang="zh-cn" color-mode="light">

  <head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1" />
  <meta name="keywords" content="" />
  <meta name="author" content="郁涛丶" />
  <meta name="description" content="" />
  
  
  <title>
    
      XV6阅读笔记 
      
      
      |
    
     郁涛丶&#39;s Blog
  </title>

  
    <link rel="apple-touch-icon" href="/images/favicon.png">
    <link rel="icon" href="/images/favicon.png">
  

  <!-- Raleway-Font -->
  <link href="https://fonts.googleapis.com/css?family=Raleway&display=swap" rel="stylesheet">

  <!-- hexo site css -->
  
<link rel="stylesheet" href="/css/color-scheme.css">
<link rel="stylesheet" href="/css/base.css">
<link rel="stylesheet" href="//at.alicdn.com/t/font_1886449_67xjft27j1l.css">
<link rel="stylesheet" href="/css/github-markdown.css">
<link rel="stylesheet" href="/css/highlight.css">
<link rel="stylesheet" href="/css/comments.css">

  <!-- 代码块风格 -->
  
    
<link rel="stylesheet" href="/css/figcaption/mac-block.css">

  

  <!-- jquery3.3.1 -->
  
    <script defer type="text/javascript" src="/plugins/jquery.min.js"></script>
  

  <!-- fancybox -->
  
    <link href="/plugins/jquery.fancybox.min.css" rel="stylesheet">
    <script defer type="text/javascript" src="/plugins/jquery.fancybox.min.js"></script>
  
  
<script src="/js/fancybox.js"></script>


  

  <script>
    var html = document.documentElement
    const colorMode = localStorage.getItem('color-mode')
    if (colorMode) {
      document.documentElement.setAttribute('color-mode', colorMode)
    }
  </script>
<!-- hexo injector head_end start -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.css">

<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/hexo-math@4.0.0/dist/style.css">
<!-- hexo injector head_end end --><meta name="generator" content="Hexo 5.4.0"><link rel="alternate" href="/atom.xml" title="郁涛丶's Blog" type="application/atom+xml">
</head>


  <body>
    <div id="app">
      <div class="header">
  <div class="avatar">
    <a href="/">
      <!-- 头像取消懒加载，添加no-lazy -->
      
        <img src="/images/avatar.png" alt="">
      
    </a>
    <div class="nickname"><a href="/">Ghostasky</a></div>
  </div>
  <div class="navbar">
    <ul>
      
        <li class="nav-item" data-path="/">
          <a href="/">Home</a>
        </li>
      
        <li class="nav-item" data-path="/archives/">
          <a href="/archives/">Archives</a>
        </li>
      
        <li class="nav-item" data-path="/categories/">
          <a href="/categories/">Categories</a>
        </li>
      
        <li class="nav-item" data-path="/tags/">
          <a href="/tags/">Tags</a>
        </li>
      
        <li class="nav-item" data-path="/about/">
          <a href="/about/">About</a>
        </li>
      
    </ul>
  </div>
</div>


<script src="/js/activeNav.js"></script>



      <div class="flex-container">
        <!-- 文章详情页，展示文章具体内容，url形式：https://yoursite/文章标题/ -->
<!-- 同时为「标签tag」，「朋友friend」，「分类categories」，「关于about」页面的承载页面，具体展示取决于page.type -->


    <!-- LaTex Display -->

  
    <script async type="text/javascript" src="https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-chtml.js"></script>
  
  <script>
    MathJax = {
      tex: {
        inlineMath: [['$', '$'], ['\\(', '\\)']]
      }
    }
  </script>


        
            
                <!-- clipboard -->

  
    <script async type="text/javascript" src="/plugins/clipboard.min.js"></script>
  
  
<script src="/js/codeCopy.js"></script>



                    
                        
                                
                                        
                                                
                                                        
                                                            <!-- 文章内容页 url形式：https://yoursite/文章标题/ -->
                                                            <div class="container post-details" id="post-details">
                                                                <div class="post-content">
                                                                    <div class="post-title">
                                                                        XV6阅读笔记
                                                                    </div>
                                                                    <div class="post-attach">
                                                                        <span class="post-pubtime">
        <i class="iconfont icon-updatetime" title="Update time"></i>
        2022-07-12
      </span>

                                                                        <span class="post-pubtime"> 本文共10.5k字 </span>

                                                                        <span class="post-pubtime">
        大约需要65min
      </span>

                                                                        
                                                                                    <span class="post-categories">
        <i class="iconfont icon-bookmark" title="Categories"></i>
        
        <span class="span--category">
          <a href="/categories/Technology/" title="Technology">
            <b>#</b> Technology
          </a>
        </span>
                                                                                    
                                                                                        </span>
                                                                                        
                                                                            <span class="post-tags">
        <i class="iconfont icon-tags" title="Tags"></i>
        
        <span class="span--tag">
          <a href="/tags/OS/" title="OS">
            <b>#</b> OS
          </a>
        </span>
                                                                            
                                                                                </span>
                                                                                
                                                                    </div>
                                                                    <div class="markdown-body">
                                                                        <p>[toc]</p>
<blockquote>
<p>  英文版：<a target="_blank" rel="noopener" href="https://pdos.csail.mit.edu/6.828/2020/xv6/book-riscv-rev1.pdf">https://pdos.csail.mit.edu/6.828/2020/xv6/book-riscv-rev1.pdf</a></p>
<p>  中文版：<a target="_blank" rel="noopener" href="http://xv6.dgs.zone/">http://xv6.dgs.zone/</a></p>
</blockquote>
<h1 id="Chapter-1-Operating-system-interfaces"><a href="#Chapter-1-Operating-system-interfaces" class="headerlink" title="Chapter 1:Operating system interfaces"></a>Chapter 1:Operating system interfaces</h1><blockquote>
<p>  xv6 是 MIT 开发的一个教学用的完整的类 Unix 操作系统，并且在 MIT 的操作系统课程 <a target="_blank" rel="noopener" href="http://pdos.csail.mit.edu/6.828/2012/xv6.html">6.828</a> 中使用</p>
<p>  xv6 是 Dennis Ritchie 和 Ken Thompson 合著的 Unix Version 6（v6）操作系统的重新实现。xv6 在一定程度上遵守 v6 的结构和风格，但它是用 ANSI C 实现的，并且是基于 x86 多核处理器的。</p>
</blockquote>
<p>进程通过<strong>系统调用</strong>使用内核服务。系统调用会进入内核，让内核执行服务然后返回。所以进程总是在用户空间和内核空间之间交替运行。</p>
<p>内核使用了CPU 的硬件保护机制来保证用户进程只能访问自己的内存空间。内核拥有实现保护机制所需的硬件权限(hardware privileges)，而用户程序没有这些权限。当一个用户程序进行一次系统调用时，硬件会提升特权级并且开始执行一些内核中预定义的功能。</p>
<p>xv6中提供的系统调用(部分unix)：</p>
<table>
<thead>
<tr>
<th>系统调用</th>
<th>描述</th>
</tr>
</thead>
<tbody><tr>
<td>fork()</td>
<td>创建进程</td>
</tr>
<tr>
<td>exit()</td>
<td>结束当前进程</td>
</tr>
<tr>
<td>wait()</td>
<td>等待子进程结束</td>
</tr>
<tr>
<td>kill(pid)</td>
<td>结束 pid 所指进程</td>
</tr>
<tr>
<td>getpid()</td>
<td>获得当前进程 pid</td>
</tr>
<tr>
<td>sleep(n)</td>
<td>睡眠 n 秒</td>
</tr>
<tr>
<td>exec(filename, *argv)</td>
<td>加载并执行一个文件</td>
</tr>
<tr>
<td>sbrk(n)</td>
<td>为进程内存空间增加 n 字节</td>
</tr>
<tr>
<td>open(filename, flags)</td>
<td>打开文件，flags 指定读&#x2F;写模式</td>
</tr>
<tr>
<td>read(fd, buf, n)</td>
<td>从文件中读 n 个字节到 buf</td>
</tr>
<tr>
<td>write(fd, buf, n)</td>
<td>从 buf 中写 n 个字节到文件</td>
</tr>
<tr>
<td>close(fd)</td>
<td>关闭打开的 fd</td>
</tr>
<tr>
<td>dup(fd)</td>
<td>复制 fd</td>
</tr>
<tr>
<td>pipe( p)</td>
<td>创建管道， 并把读和写的 fd 返回到p</td>
</tr>
<tr>
<td>chdir(dirname)</td>
<td>改变当前目录</td>
</tr>
<tr>
<td>mkdir(dirname)</td>
<td>创建新的目录</td>
</tr>
<tr>
<td>mknod(name, major, minor)</td>
<td>创建设备文件</td>
</tr>
<tr>
<td>fstat(fd)</td>
<td>返回文件信息</td>
</tr>
<tr>
<td>link(f1, f2)</td>
<td>给 f1 创建一个新名字(f2)</td>
</tr>
<tr>
<td>unlink(filename)</td>
<td>删除文件</td>
</tr>
</tbody></table>
<p>xv6 shell的具体实现在<code>user/sh.c</code></p>
<h2 id="1-1-进程和内存"><a href="#1-1-进程和内存" class="headerlink" title="1.1 进程和内存"></a>1.1 进程和内存</h2><p><code>fork</code> 函数在父进程、子进程中都返回（一次调用两次返回），对于父进程它返回子进程的 pid，对于子进程它返回 0。</p>
<figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">int</span> pid;</span><br><span class="line">pid = fork();</span><br><span class="line"><span class="keyword">if</span>(pid &gt; <span class="number">0</span>)&#123;</span><br><span class="line">    <span class="built_in">printf</span>(<span class="string">&quot;parent: child=%d\n&quot;</span>, pid);</span><br><span class="line">    pid = wait();</span><br><span class="line">    <span class="built_in">printf</span>(<span class="string">&quot;child %d is done\n&quot;</span>, pid);</span><br><span class="line">&#125; <span class="keyword">else</span> <span class="keyword">if</span>(pid == <span class="number">0</span>)&#123;</span><br><span class="line">    <span class="built_in">printf</span>(<span class="string">&quot;child: exiting\n&quot;</span>);</span><br><span class="line">    <span class="built_in">exit</span>();</span><br><span class="line">&#125; <span class="keyword">else</span> &#123;</span><br><span class="line">    <span class="built_in">printf</span>(<span class="string">&quot;fork error\n&quot;</span>);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>系统调用 <code>wait</code> 会返回一个<strong>当前进程已退出的子进程</strong>，如果没有子进程退出，<code>wait</code> 会等候直到有一个子进程退出。</p>
<p>父子进程拥有不同的内存空间和寄存器，改变一个进程中的变量不会影响另一个进程，这一点在代码中有体现。</p>
<p><code>exec</code>系统调用，从某个<em>文件</em>（通常是可执行文件）里读取内存镜像，并将其替换到调用它的进程的内存空间，这样的话，在执行<code>exec</code>系统调用后不返回，而是直接执行调用的elf文件：</p>
<figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">char</span> *argv[<span class="number">3</span>];</span><br><span class="line">argv[<span class="number">0</span>] = <span class="string">&quot;echo&quot;</span>;</span><br><span class="line">argv[<span class="number">1</span>] = <span class="string">&quot;hello&quot;</span>;</span><br><span class="line">argv[<span class="number">2</span>] = <span class="number">0</span>;</span><br><span class="line">exec(<span class="string">&quot;/bin/echo&quot;</span>, argv);</span><br><span class="line"><span class="built_in">printf</span>(<span class="string">&quot;exec error\n&quot;</span>);<span class="comment">//注意这里，不返回，走这一步说明出问题了</span></span><br></pre></td></tr></table></figure>

<p>下面是xv6 shell的代码：</p>
<figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">main</span><span class="params">(<span class="keyword">void</span>)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">	<span class="keyword">static</span> <span class="keyword">char</span> buf[<span class="number">100</span>];</span><br><span class="line">	<span class="keyword">int</span> fd;</span><br><span class="line">	<span class="comment">// Ensure that three file descriptors are open.</span></span><br><span class="line">	<span class="keyword">while</span> ((fd = open(<span class="string">&quot;console&quot;</span>, O_RDWR)) &gt;= <span class="number">0</span>)</span><br><span class="line">	&#123;</span><br><span class="line">		<span class="keyword">if</span> (fd &gt;= <span class="number">3</span>)</span><br><span class="line">		&#123;</span><br><span class="line">			close(fd);</span><br><span class="line">			<span class="keyword">break</span>;</span><br><span class="line">		&#125;</span><br><span class="line">	&#125;</span><br><span class="line">	<span class="comment">// Read and run input commands.</span></span><br><span class="line">	<span class="keyword">while</span> (getcmd(buf, <span class="keyword">sizeof</span>(buf)) &gt;= <span class="number">0</span>)<span class="comment">//getcmd 读取命令行的输入</span></span><br><span class="line">	&#123;</span><br><span class="line">		<span class="keyword">if</span> (buf[<span class="number">0</span>] == <span class="string">&#x27;c&#x27;</span> &amp;&amp; buf[<span class="number">1</span>] == <span class="string">&#x27;d&#x27;</span> &amp;&amp; buf[<span class="number">2</span>] == <span class="string">&#x27; &#x27;</span>)</span><br><span class="line">		&#123;</span><br><span class="line">			<span class="comment">// Chdir must be called by the parent, not the child.</span></span><br><span class="line">			buf[<span class="built_in">strlen</span>(buf) - <span class="number">1</span>] = <span class="number">0</span>; <span class="comment">// chop \n</span></span><br><span class="line">			<span class="keyword">if</span> (chdir(buf + <span class="number">3</span>) &lt; <span class="number">0</span>)</span><br><span class="line">				<span class="built_in">fprintf</span>(<span class="number">2</span>, <span class="string">&quot;cannot cd %s\n&quot;</span>, buf + <span class="number">3</span>);</span><br><span class="line">			<span class="keyword">continue</span>;</span><br><span class="line">		&#125;</span><br><span class="line">		<span class="keyword">if</span> (fork1() == <span class="number">0</span>)<span class="comment">//调用fork</span></span><br><span class="line">			runcmd(parsecmd(buf));</span><br><span class="line">		wait(<span class="number">0</span>);<span class="comment">//父进程等待子进程退出</span></span><br><span class="line">	&#125;</span><br><span class="line">	<span class="built_in">exit</span>(<span class="number">0</span>);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p><code>fork</code>的时候要拷贝elf的内容，空间不够的话：<code>sbrk(n)</code> 来增加 n 字节的数据内存。<code>sbrk</code> 返回新的内存的地址。</p>
<h2 id="1-2-I-x2F-O-和文件描述符"><a href="#1-2-I-x2F-O-和文件描述符" class="headerlink" title="1.2 I&#x2F;O 和文件描述符"></a>1.2 I&#x2F;O 和文件描述符</h2><p><strong>文件描述符</strong>是一个整数，它代表了一个进程可以读写的被内核管理的对象。进程可以通过多种方式获得一个文件描述符，如打开文件、目录、设备，或者创建一个管道（pipe），或者复制已经存在的文件描述符。</p>
<p>每个进程都有一张表，而 xv6 内核就以文件描述符作为这张表的索引，所以每个进程都有一个从0开始的文件描述符空间。见下图，但是这里不深究，也就看个大概，真想深究的话，去跟iofile的源码。</p>
<p>文件描述符0读入（标准输入），从文件描述符1输出（标准输出），从文件描述符2输出错误（标准错误输出）。</p>
<p><img src="/2022/07/12/XV6/image-20220707211016555.png"></p>
<p>系统调用 <code>close</code> 会释放一个文件描述符，使得它之后可以被 <code>open</code>, <code>pipe</code>, <code>dup</code> 等调用重用。新分配的文件描述符始终是当前进程中编号最少的未使用描述符。</p>
<p><code>fork</code> 会复制父进程的文件描述符和内存，所以子进程和父进程的文件描述符一模一样。</p>
<p><code>exec</code> 会替换调用它的进程的内存但是会保留它的文件描述符表。</p>
<figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">char</span>* argv[<span class="number">2</span>];</span><br><span class="line">argv[<span class="number">0</span>] = <span class="string">&quot;cat&quot;</span>;</span><br><span class="line">argv[<span class="number">1</span>] = <span class="number">0</span>;</span><br><span class="line"><span class="keyword">if</span> (fork() == <span class="number">0</span>) &#123;</span><br><span class="line">    close(<span class="number">0</span>);</span><br><span class="line">    open(<span class="string">&quot;input.txt&quot;</span>, O_RDONLY);</span><br><span class="line">    exec(<span class="string">&quot;cat&quot;</span>, argv);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>在子进程关闭文件描述符0之后，<code>open</code>使用新打开的input.txt(0文件描述符为0)。<code>cat</code>然后执行文件描述符0(标准输入)，但引用的是input.txt。父进程的文件描述符不会被这个序列改变，因为它只修改子进程的描述符。(这里还是挺有意思的)</p>
<p><code>open</code>的第二个参数由一组标志组成，这些标志以位表示，用于控制打开的操作。在<code>kernel/fcntl.h</code>中</p>
<figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="meta-keyword">define</span> O_RDONLY  0x000</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">define</span> O_WRONLY  0x001</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">define</span> O_RDWR    0x002</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">define</span> O_CREATE  0x200<span class="comment">//如果文件不存在则创建文件</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">define</span> O_TRUNC   0x400<span class="comment">//将文件截断为零长度</span></span></span><br></pre></td></tr></table></figure>

<p><code>dup</code>系统调用复制一个现有的文件描述符，返回一个引用自同一个底层I&#x2F;O对象的新文件描述符。</p>
<p>helloword的另一种写法：</p>
<figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">fd = dup(<span class="number">1</span>);</span><br><span class="line">write(<span class="number">1</span>, <span class="string">&quot;hello&quot;</span>, <span class="number">6</span>);</span><br><span class="line">write(fd, <span class="string">&quot;world\n&quot;</span>, <span class="number">6</span>);</span><br></pre></td></tr></table></figure>

<h2 id="1-3-管道"><a href="#1-3-管道" class="headerlink" title="1.3 管道"></a>1.3 管道</h2><p>管道是一个小的内核缓冲区，它以文件描述符对的形式提供给进程，一个用于写操作，一个用于读操作。从管道的一端写的数据可以从管道的另一端读取。提供了一种<strong>进程间</strong>交互的方式。就是队列，0是读，1是写。</p>
<figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">int</span> p[<span class="number">2</span>];</span><br><span class="line"><span class="keyword">char</span> *argv[<span class="number">2</span>];</span><br><span class="line">argv[<span class="number">0</span>] = <span class="string">&quot;wc&quot;</span>;</span><br><span class="line">argv[<span class="number">1</span>] = <span class="number">0</span>;</span><br><span class="line">pipe(p);</span><br><span class="line"><span class="keyword">if</span>(fork() == <span class="number">0</span>) &#123;</span><br><span class="line">    close(<span class="number">0</span>);</span><br><span class="line">    dup(p[<span class="number">0</span>]);<span class="comment">//调用close和dup使文件描述符0指向管道的读取端</span></span><br><span class="line">    close(p[<span class="number">0</span>]);</span><br><span class="line">    close(p[<span class="number">1</span>]);</span><br><span class="line">    exec(<span class="string">&quot;/bin/wc&quot;</span>, argv);</span><br><span class="line">&#125; <span class="keyword">else</span> &#123;</span><br><span class="line">    write(p[<span class="number">1</span>], <span class="string">&quot;hello world\n&quot;</span>, <span class="number">12</span>);</span><br><span class="line">    close(p[<span class="number">0</span>]);</span><br><span class="line">    close(p[<span class="number">1</span>]);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p><code>link</code>系统调用创建另一个文件名，该文件名指向与现有文件相同的inode。</p>
<figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">open(<span class="string">&quot;a&quot;</span>, O_CREATE | O_WRONLY);</span><br><span class="line">link(<span class="string">&quot;a&quot;</span>, <span class="string">&quot;b&quot;</span>);</span><br><span class="line"><span class="comment">// 创建了一个既叫做 a 又叫做 b 的新文件</span></span><br><span class="line">unlink(<span class="string">&quot;a&quot;</span>)，</span><br></pre></td></tr></table></figure>

<p>读写 <code>a</code> 就相当于读写 <code>b</code>。每一个 inode 都由一个唯一的 <code>inode 号</code> 直接确定。在上面这段代码中，我们可以通过 <code>fstat</code> 知道 <code>a</code> 和 <code>b</code> 都指向同样的内容：<code>a</code> 和 <code>b</code> 都会返回同样的 inode 号（<code>ino</code>），并且 <code>nlink</code> 数会设置为2。</p>
<p>系统调用 <code>unlink</code> 从文件系统移除一个文件名。一个文件的 inode 和磁盘空间只有当它的链接数变为 0 的时候才会被清空，也就是没有一个文件再指向它。</p>
<h2 id="1-4-文件系统"><a href="#1-4-文件系统" class="headerlink" title="1.4 文件系统"></a>1.4 文件系统</h2><p>xv6 文件系统提供文件和目录，文件就是一个简单的字节数组，而目录包含指向文件和其他目录的引用。</p>
<p>不从 <code>/</code> 开始的目录表示的是相对调用进程当前目录的目录，调用进程的当前目录可以通过 <code>chdir</code> 这个系统调用进行改变。</p>
<figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">chdir(<span class="string">&quot;/a&quot;</span>);</span><br><span class="line">chdir(<span class="string">&quot;b&quot;</span>);</span><br><span class="line">open(<span class="string">&quot;c&quot;</span>, O_RDONLY);</span><br><span class="line">open(<span class="string">&quot;/a/b/c&quot;</span>, O_RDONLY);</span><br></pre></td></tr></table></figure>

<p><code>mknod</code>创建一个引用设备的特殊文件。与设备文件相关联的是主设备号和次设备号(<code>mknod</code>的两个参数)，它们唯一地标识了一个内核设备。当进程稍后打开设备文件时，内核将使用内核设备实现<code>read</code>和<code>write</code>系统调用，而不是使用文件系统。</p>
<figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">mkdir(<span class="string">&quot;/dir&quot;</span>);</span><br><span class="line">fd = open(<span class="string">&quot;/dir/file&quot;</span>, O_CREATE | O_WRONLY);</span><br><span class="line">close(fd);</span><br><span class="line">mknod(<span class="string">&quot;/console&quot;</span>, <span class="number">1</span>, <span class="number">1</span>);</span><br></pre></td></tr></table></figure>

<p>同一个底层文件（叫做inode，索引结点）可以有多个名字（叫做link，链接）。每个链接都由目录中的一个条目组成;该条目包含一个文件名和一个inode引用。Inode保存有关文件的元数据（用于解释或帮助理解信息的数据），包括其类型(文件&#x2F;目录&#x2F;设备)、长度、文件内容在磁盘上的位置以及指向文件的链接数。</p>
<p><code>fstat</code> 可以获取一个文件描述符指向的文件的信息。它填充一个名为 <code>stat</code> 的结构体，它在 <code>stat.h</code> 中定义为：</p>
<figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="meta-keyword">define</span> T_DIR     1   <span class="comment">// Directory</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">define</span> T_FILE    2   <span class="comment">// File</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">define</span> T_DEVICE  3   <span class="comment">// Device</span></span></span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">stat</span> &#123;</span></span><br><span class="line">  <span class="keyword">int</span> dev;     <span class="comment">// File system&#x27;s disk device</span></span><br><span class="line">  uint ino;    <span class="comment">// Inode number,Inode编号</span></span><br><span class="line">  <span class="keyword">short</span> type;  <span class="comment">// Type of file</span></span><br><span class="line">  <span class="keyword">short</span> nlink; <span class="comment">// Number of links to file，指向文件的链接数</span></span><br><span class="line">  uint64 size; <span class="comment">// Size of file in bytes</span></span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure>







<h1 id="Chapter-2-Operating-system-organization"><a href="#Chapter-2-Operating-system-organization" class="headerlink" title="Chapter 2:Operating system organization"></a>Chapter 2:Operating system organization</h1><h2 id="2-1-xv6架构"><a href="#2-1-xv6架构" class="headerlink" title="2.1 xv6架构"></a>2.1 xv6架构</h2><table>
<thead>
<tr>
<th><strong>文件</strong></th>
<th><strong>描述</strong></th>
</tr>
</thead>
<tbody><tr>
<td><strong>bio.c</strong></td>
<td>文件系统的磁盘块缓存</td>
</tr>
<tr>
<td><strong>def.h</strong></td>
<td>模块间的接口定义</td>
</tr>
<tr>
<td><strong>console.c</strong></td>
<td>连接到用户的键盘和屏幕</td>
</tr>
<tr>
<td><strong>entry.S</strong></td>
<td>首次启动指令</td>
</tr>
<tr>
<td><strong>exec.</strong></td>
<td><code>exec()</code>系统调用</td>
</tr>
<tr>
<td><strong>file.c</strong></td>
<td>文件描述符支持</td>
</tr>
<tr>
<td><strong>fs.c</strong></td>
<td>文件系统</td>
</tr>
<tr>
<td><strong>kalloc.c</strong></td>
<td>物理页面分配器</td>
</tr>
<tr>
<td><strong>kernelvec.S</strong></td>
<td>处理来自内核的陷入指令以及计时器中断</td>
</tr>
<tr>
<td><strong>log.c</strong></td>
<td>文件系统日志记录以及崩溃修复</td>
</tr>
<tr>
<td><strong>main.c</strong></td>
<td>在启动过程中控制其他模块初始化</td>
</tr>
<tr>
<td><strong>pipe.c</strong></td>
<td>管道</td>
</tr>
<tr>
<td><strong>plic.c</strong></td>
<td>RISC-V中断控制器</td>
</tr>
<tr>
<td><strong>printf.c</strong></td>
<td>格式化输出到控制台</td>
</tr>
<tr>
<td><strong>proc.c</strong></td>
<td>进程和调度</td>
</tr>
<tr>
<td><strong>sleeplock.c</strong></td>
<td>Locks that yield the CPU</td>
</tr>
<tr>
<td><strong>spinlock.c</strong></td>
<td>Locks that don’t yield the CPU.</td>
</tr>
<tr>
<td><strong>start.c</strong></td>
<td>早期机器模式启动代码</td>
</tr>
<tr>
<td><strong>string.c</strong></td>
<td>字符串和字节数组库</td>
</tr>
<tr>
<td><strong>swtch.c</strong></td>
<td>线程切换</td>
</tr>
<tr>
<td><strong>syscall.c</strong></td>
<td>Dispatch system calls to handling function.</td>
</tr>
<tr>
<td><strong>sysfile.c</strong></td>
<td>文件相关的系统调用</td>
</tr>
<tr>
<td><strong>sysproc.c</strong></td>
<td>进程相关的系统调用</td>
</tr>
<tr>
<td><strong>trampoline.S</strong></td>
<td>用于在用户和内核之间切换的汇编代码</td>
</tr>
<tr>
<td><strong>trap.c</strong></td>
<td>对陷入指令和中断进行处理并返回的C代码</td>
</tr>
<tr>
<td><strong>uart.c</strong></td>
<td>串口控制台设备驱动程序</td>
</tr>
<tr>
<td><strong>virtio_disk.c</strong></td>
<td>磁盘设备驱动程序</td>
</tr>
<tr>
<td><strong>vm.c</strong></td>
<td>管理页表和地址空间</td>
</tr>
</tbody></table>
<h2 id="2-2进程概述"><a href="#2-2进程概述" class="headerlink" title="2.2进程概述"></a>2.2进程概述</h2><p>xv6 使用页表（由硬件实现）来为每个进程提供其独有的地址空间(实现隔离)。</p>
<p>页表将<em>虚拟地址</em>（x86 指令所使用的地址）翻译（或说“映射”）为<em>物理地址</em>（处理器芯片向主存发送的地址）。</p>
<p>Xv6为每个进程维护一个单独的页表，定义了该进程的地址空间。</p>
<p> RISC-V上的指针有64位宽；硬件在页表中查找虚拟地址时只使用低39位；xv6只使用这39位中的38位。因此，最大地址是2^38-1&#x3D;0x3fffffffff，即<code>MAXVA</code>，在<strong>kernel&#x2F;riscv.h</strong>中定义：</p>
<figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// one beyond the highest possible virtual address.</span></span><br><span class="line"><span class="comment">// MAXVA is actually one bit less than the max allowed by</span></span><br><span class="line"><span class="comment">// Sv39, to avoid having to sign-extend virtual addresses</span></span><br><span class="line"><span class="comment">// that have the high bit set.</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">define</span> MAXVA (1L &lt;&lt; (9 + 9 + 9 + 12 - 1))</span></span><br></pre></td></tr></table></figure>

<p>xv6为<code>trampoline</code>（用于在用户和内核之间切换）和映射进程切换到内核的<code>trapframe</code>分别保留了一个页面:</p>
<p><img src="/2022/07/12/XV6/image-20220713121236349.png"></p>
<p>xv6内核为每个进程维护许多状态片段，并将它们聚集到一个<code>proc</code>结构体中<code>kernel/proc.h</code>：</p>
<figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// Per-process state</span></span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">proc</span> &#123;</span></span><br><span class="line">  <span class="class"><span class="keyword">struct</span> <span class="title">spinlock</span> <span class="title">lock</span>;</span></span><br><span class="line"></span><br><span class="line">  <span class="comment">// p-&gt;lock must be held when using these:</span></span><br><span class="line">  <span class="class"><span class="keyword">enum</span> <span class="title">procstate</span> <span class="title">state</span>;</span>        <span class="comment">// Process state,表明进程是已分配、就绪态、运行态、等待I/O中（阻塞态）还是退出</span></span><br><span class="line">  <span class="keyword">void</span> *chan;                  <span class="comment">// If non-zero, sleeping on chan</span></span><br><span class="line">  <span class="keyword">int</span> killed;                  <span class="comment">// If non-zero, have been killed</span></span><br><span class="line">  <span class="keyword">int</span> xstate;                  <span class="comment">// Exit status to be returned to parent&#x27;s wait</span></span><br><span class="line">  <span class="keyword">int</span> pid;                     <span class="comment">// Process ID</span></span><br><span class="line"></span><br><span class="line">  <span class="comment">// wait_lock must be held when using this:</span></span><br><span class="line">  <span class="class"><span class="keyword">struct</span> <span class="title">proc</span> *<span class="title">parent</span>;</span>         <span class="comment">// Parent process</span></span><br><span class="line"></span><br><span class="line">  <span class="comment">// these are private to the process, so p-&gt;lock need not be held.</span></span><br><span class="line">  uint64 kstack;               <span class="comment">// Virtual address of kernel stack</span></span><br><span class="line">  uint64 sz;                   <span class="comment">// Size of process memory (bytes)</span></span><br><span class="line">  <span class="keyword">pagetable_t</span> pagetable;       <span class="comment">// User page table</span></span><br><span class="line">  <span class="class"><span class="keyword">struct</span> <span class="title">trapframe</span> *<span class="title">trapframe</span>;</span> <span class="comment">// data page for trampoline.S</span></span><br><span class="line">  <span class="class"><span class="keyword">struct</span> <span class="title">context</span> <span class="title">context</span>;</span>      <span class="comment">// swtch() here to run process</span></span><br><span class="line">  <span class="class"><span class="keyword">struct</span> <span class="title">file</span> *<span class="title">ofile</span>[<span class="title">NOFILE</span>];</span>  <span class="comment">// Open files</span></span><br><span class="line">  <span class="class"><span class="keyword">struct</span> <span class="title">inode</span> *<span class="title">cwd</span>;</span>           <span class="comment">// Current directory</span></span><br><span class="line">  <span class="keyword">char</span> name[<span class="number">16</span>];               <span class="comment">// Process name (debugging)</span></span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure>

<p>每个进程都有一个执行线程（或简称线程）来执行进程的指令。一个线程可以挂起并且稍后再恢复</p>
<p>每个线程有两个栈区，一个user的stack，一个kernel的stack：<code>p-&gt;kstack</code></p>
<p>当进程执行用户指令时，只有它的用户栈在使用，它的内核栈是空的。当进程进入内核（由于系统调用或中断）时，内核代码在进程的内核堆栈上执行；当一个进程在内核中时，它的用户堆栈仍然包含保存的数据，只是不处于活动状态。进程的线程在主动使用它的用户栈和内核栈之间交替。内核栈是独立的（并且不受用户代码的保护），因此即使一个进程破坏了它的用户栈，内核依然可以正常运行。</p>
<p>一个进程可以通过执行RISC-V的<code>ecall</code>指令进行系统调用，该指令提升硬件特权级别，并将程序计数器（PC）更改为内核定义的入口点，，入口点的代码切换到内核栈，执行实现系统调用的内核指令，当系统调用完成时，内核切换回用户栈，并通过调用<code>sret</code>指令返回用户空间，该指令降低了硬件特权级别，并在系统调用指令刚结束时恢复执行用户指令。进程的线程可以在内核中“阻塞”等待I&#x2F;O，并在I&#x2F;O完成后恢复到中断的位置。</p>
<h2 id="2-3-启动XV6的第一个程序"><a href="#2-3-启动XV6的第一个程序" class="headerlink" title="2.3 启动XV6的第一个程序"></a>2.3 启动XV6的第一个程序</h2><p>初始化自己并运行一个存储在只读内存中的引导加载程序。引导加载程序将xv6内核加载到内存中，之后在机器模式下CPU从<code>_entry</code>开始运行，动时页式硬件（paging hardware）处于禁用模式：也就是说虚拟地址将直接映射到物理地址。</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line">.section .text</span><br><span class="line">_entry:</span><br><span class="line">	# set up a stack for C.</span><br><span class="line">	# stack0 is declared in start.c,</span><br><span class="line">	# with a 4096-byte stack per CPU.</span><br><span class="line">	# sp = stack0 + (hartid * 4096)</span><br><span class="line">	la sp, stack0</span><br><span class="line">	li a0, 1024*4</span><br><span class="line">	csrr a1, mhartid</span><br><span class="line">	addi a1, a1, 1</span><br><span class="line">	mul a0, a0, a1</span><br><span class="line">	add sp, sp, a0</span><br><span class="line">	# jump to start() in start.c</span><br><span class="line">	call start</span><br></pre></td></tr></table></figure>

<p>设置好栈区之后调用start，<code>start</code>执行一些仅在机器模式下允许的配置，然后切换到管理模式(<code>mret</code>指令)。</p>
<figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">start</span><span class="params">()</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">	<span class="comment">// set M Previous Privilege mode to Supervisor, for mret.</span></span><br><span class="line">	<span class="keyword">unsigned</span> <span class="keyword">long</span> x = r_mstatus();</span><br><span class="line">	x &amp;= ~MSTATUS_MPP_MASK;</span><br><span class="line">	x |= MSTATUS_MPP_S;</span><br><span class="line">	w_mstatus(x);</span><br><span class="line"></span><br><span class="line">	<span class="comment">// set M Exception Program Counter to main, for mret.</span></span><br><span class="line">	<span class="comment">// requires gcc -mcmodel=medany</span></span><br><span class="line">	w_mepc((uint64)main);</span><br><span class="line"></span><br><span class="line">	<span class="comment">// disable paging for now.</span></span><br><span class="line">	w_satp(<span class="number">0</span>);</span><br><span class="line"></span><br><span class="line">	<span class="comment">// delegate all interrupts and exceptions to supervisor mode.</span></span><br><span class="line">	w_medeleg(<span class="number">0xffff</span>);</span><br><span class="line">	w_mideleg(<span class="number">0xffff</span>);</span><br><span class="line">	w_sie(r_sie() | SIE_SEIE | SIE_STIE | SIE_SSIE);</span><br><span class="line"></span><br><span class="line">	<span class="comment">// ask for clock interrupts.</span></span><br><span class="line">	timerinit();</span><br><span class="line"></span><br><span class="line">	<span class="comment">// keep each CPU&#x27;s hartid in its tp register, for cpuid().</span></span><br><span class="line">	<span class="keyword">int</span> id = r_mhartid();</span><br><span class="line">	w_tp(id);</span><br><span class="line"></span><br><span class="line">	<span class="comment">// switch to supervisor mode and jump to main().</span></span><br><span class="line">	<span class="function"><span class="keyword">asm</span> <span class="title">volatile</span><span class="params">(<span class="string">&quot;mret&quot;</span>)</span></span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>调用<code>mret</code>后会导致PC的值为main的地址<code>kernel/main.c</code></p>
<blockquote>
<p>  <strong>注：</strong><code>mret</code>执行返回，返回到先前状态，由于<code>start</code>函数将前模式改为了管理模式且返回地址改为了<code>main</code>,因此<code>mret</code>将返回到<code>main</code>函数，并以管理模式运行</p>
</blockquote>
<p><code>main</code>初始化几个设备和子系统后，调用<code>userinit</code>创建第一个进程（<code>kernel/proc.c</code>），第一个进程执行一个用RISC-V程序集写的小型程序：<strong>initcode. S</strong>，调用<code>exec</code>系统调用重新进入内核，<code>exec</code>使用用 <code>/init</code>（<strong>user&#x2F;init.c</strong>）替换当前进程的内存和寄存器。一旦内核完成<code>exec</code>，它就返回<code>/init</code>进程中的用户空间。</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br></pre></td><td class="code"><pre><span class="line"># Initial process that execs /init.</span><br><span class="line"># This code runs in user space.</span><br><span class="line"></span><br><span class="line">#include &quot;syscall.h&quot;</span><br><span class="line"></span><br><span class="line"># exec(init, argv)</span><br><span class="line">.globl start</span><br><span class="line">start:</span><br><span class="line">        la a0, init</span><br><span class="line">        la a1, argv</span><br><span class="line">        li a7, SYS_exec</span><br><span class="line">        ecall</span><br><span class="line"></span><br><span class="line"># for(;;) exit();</span><br><span class="line">exit:</span><br><span class="line">        li a7, SYS_exit</span><br><span class="line">        ecall</span><br><span class="line">        jal exit</span><br><span class="line"></span><br><span class="line"># char init[] = &quot;/init\0&quot;;</span><br><span class="line">init:</span><br><span class="line">  .string &quot;/init\0&quot;</span><br><span class="line"></span><br><span class="line"># char *argv[] = &#123; init, 0 &#125;;</span><br><span class="line">.p2align 2</span><br><span class="line">argv:</span><br><span class="line">  .long init</span><br><span class="line">  .long 0</span><br></pre></td></tr></table></figure>

<p>之后的<code>/init</code>：</p>
<figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//user.init.c</span></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">main</span><span class="params">(<span class="keyword">void</span>)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">	<span class="keyword">int</span> pid, wpid;</span><br><span class="line"></span><br><span class="line">	<span class="keyword">if</span> (open(<span class="string">&quot;console&quot;</span>, O_RDWR) &lt; <span class="number">0</span>)</span><br><span class="line">	&#123;</span><br><span class="line">		mknod(<span class="string">&quot;console&quot;</span>, CONSOLE, <span class="number">0</span>);</span><br><span class="line">		open(<span class="string">&quot;console&quot;</span>, O_RDWR);</span><br><span class="line">	&#125;</span><br><span class="line">	dup(<span class="number">0</span>); <span class="comment">// stdout</span></span><br><span class="line">	dup(<span class="number">0</span>); <span class="comment">// stderr</span></span><br><span class="line"></span><br><span class="line">	<span class="keyword">for</span> (;;)</span><br><span class="line">	&#123;</span><br><span class="line">		<span class="built_in">printf</span>(<span class="string">&quot;init: starting sh\n&quot;</span>);</span><br><span class="line">		pid = fork();</span><br><span class="line">		<span class="keyword">if</span> (pid &lt; <span class="number">0</span>)</span><br><span class="line">		&#123;</span><br><span class="line">			<span class="built_in">printf</span>(<span class="string">&quot;init: fork failed\n&quot;</span>);</span><br><span class="line">			<span class="built_in">exit</span>(<span class="number">1</span>);</span><br><span class="line">		&#125;</span><br><span class="line">		<span class="keyword">if</span> (pid == <span class="number">0</span>)</span><br><span class="line">		&#123;</span><br><span class="line">			exec(<span class="string">&quot;sh&quot;</span>, argv);</span><br><span class="line">			<span class="built_in">printf</span>(<span class="string">&quot;init: exec sh failed\n&quot;</span>);</span><br><span class="line">			<span class="built_in">exit</span>(<span class="number">1</span>);</span><br><span class="line">		&#125;</span><br><span class="line"></span><br><span class="line">		<span class="keyword">for</span> (;;)</span><br><span class="line">		&#123;</span><br><span class="line">			<span class="comment">// this call to wait() returns if the shell exits,</span></span><br><span class="line">			<span class="comment">// or if a parentless process exits.</span></span><br><span class="line">			wpid = wait((<span class="keyword">int</span> *)<span class="number">0</span>);</span><br><span class="line">			<span class="keyword">if</span> (wpid == pid)</span><br><span class="line">			&#123;</span><br><span class="line">				<span class="comment">// the shell exited; restart it.</span></span><br><span class="line">				<span class="keyword">break</span>;</span><br><span class="line">			&#125;</span><br><span class="line">			<span class="keyword">else</span> <span class="keyword">if</span> (wpid &lt; <span class="number">0</span>)</span><br><span class="line">			&#123;</span><br><span class="line">				<span class="built_in">printf</span>(<span class="string">&quot;init: wait returned an error\n&quot;</span>);</span><br><span class="line">				<span class="built_in">exit</span>(<span class="number">1</span>);</span><br><span class="line">			&#125;</span><br><span class="line">			<span class="keyword">else</span></span><br><span class="line">			&#123;</span><br><span class="line">				<span class="comment">// it was a parentless process; do nothing.</span></span><br><span class="line">			&#125;</span><br><span class="line">		&#125;</span><br><span class="line">	&#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>





<h1 id="Chapter-3-Page-tables"><a href="#Chapter-3-Page-tables" class="headerlink" title="Chapter 3:Page tables"></a>Chapter 3:Page tables</h1><p>页表是操作系统为每个进程提供自己的私有地址空间和内存的机制。页表决定了内存地址的含义，以及物理内存的哪些部分可以访问。</p>
<h2 id="3-1-分页硬件"><a href="#3-1-分页硬件" class="headerlink" title="3.1 分页硬件"></a>3.1 分页硬件</h2><p>XV6基于Sv39 RISC-V运行，只使用64位虚拟地址的低39位；而<strong>高25位不使用</strong>。在这种Sv39配置中，RISC-V页表在逻辑上是一个由$2^{27}$（134,217,728）个<strong>页表条目（Page Table Entries&#x2F;PTE）</strong>组成的数组。每个PTE包含一个44位的<strong>物理页码（Physical Page Number&#x2F;PPN）</strong>和一些标志。页式硬件通过使用虚拟地址39位中的前27位索引页表，以找到该虚拟地址对应的一个PTE，然后生成一个56位的物理地址，其前44位来自PTE中的PPN，其后12位来自原始虚拟地址。如下图3.1，页表的逻辑视图是一个简单的PTE数组如图3.2。页表通过逻辑到物理地址的转换给了操作系统控制权，转换的粒度是一个个对齐的物理块（一个物理块包含$2^{12}&#x3D;4096$字节），这样的块称为页。</p>
<p><img src="/2022/07/12/XV6/p1.png"></p>
<p><img src="/2022/07/12/XV6/p2.png"></p>
<p>如果转换地址所需的三个PTE中的任何一个不存在，页式硬件就会引发页面故障异常，并让内核来处理该异常</p>
<p>每个<code>PTE</code>包含的标志位（在<code>kernel/riscv.h</code>中定义）：</p>
<ul>
<li><code>PTE_V</code>指示PTE是否存在：如果它没有被设置，对页面的引用会导致异常（即不允许）。</li>
<li><code>PTE_R</code>控制是否允许指令读取到页面。</li>
<li><code>PTE_W</code>控制是否允许指令写入到页面。</li>
<li><code>PTE_X</code>控制CPU是否可以将页面内容解释为指令并执行它们。</li>
<li><code>PTE_U</code>控制用户模式下的指令是否被允许访问页面；如果没有设置<code>PTE_U</code>，PTE只能在管理模式下使用。</li>
</ul>
<figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="meta-keyword">define</span> PGSIZE 4096 <span class="comment">// bytes per page</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">define</span> PGSHIFT 12  <span class="comment">// bits of offset within a page</span></span></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">define</span> PGROUNDUP(sz)  (((sz)+PGSIZE-1) &amp; ~(PGSIZE-1))</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">define</span> PGROUNDDOWN(a) (((a)) &amp; ~(PGSIZE-1))</span></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">define</span> PTE_V (1L &lt;&lt; 0) <span class="comment">// valid</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">define</span> PTE_R (1L &lt;&lt; 1)</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">define</span> PTE_W (1L &lt;&lt; 2)</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">define</span> PTE_X (1L &lt;&lt; 3)</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">define</span> PTE_U (1L <span class="meta-string">&lt;&lt; 4) // 1 -&gt;</span> user can access</span></span><br></pre></td></tr></table></figure>

<p>为了告诉硬件使用页表，内核必须将根页表页的物理地址写入到<code>satp</code>寄存器中（<code>satp</code>的作用是存放根页表页在物理内存中的地址）。每个CPU都有自己的<code>satp</code>。一个CPU将使用自己的<code>satp</code>指向的页表转换后续指令生成的所有地址。每个CPU都有自己的<code>satp</code>，这样不同的CPU就可以运行不同的进程，每个CPU都有自己的页表描述的私有地址空间。</p>
<p>物理内存以一个字节为单位划为地址，称为物理地址。</p>
<h2 id="3-2-内核地址空间"><a href="#3-2-内核地址空间" class="headerlink" title="3.2 内核地址空间"></a>3.2 内核地址空间</h2><p><strong>kernel&#x2F;memlayout.h</strong>声明了xv6内核内存布局的常量，内存映射：</p>
<img src="/2022/07/12/XV6/p3.png" alt="img" style="zoom: 80%;">

<p>QEMU模拟了一台计算机，它包括从物理地址<code>0x80000000</code>开始，到<code>0x86400000</code>结束的RAM（物理内存），<code>KERNBASE</code>和<code>PHYSTOP</code>；QEMU还模拟了IO等设备。</p>
<p>有几个内核虚拟地址不是直接映射：</p>
<ul>
<li><code>trampoline page</code>。映射在虚拟地址空间的顶部；用户页表具有相同的映射。</li>
<li>内核栈页面。每个进程都有自己的内核栈，它将映射到偏高一些的地址，这样xv6在它之下就可以留下一个未映射的保护页(<strong>guard page)<strong>。保护页的PTE是无效的（也就是说<code>PTE_V</code>没有设置），所以如果内核溢出内核栈就会引发一个异常，内核触发<code>panic</code>。如果没有保护页，栈溢出将会覆盖其他内核内存，引发错误操作。（注：Guard page不会浪费物理内存，它只是占据了虚拟地址空间的一段靠后的地址，但并</strong>不映射到物理地址空间</strong>。）</li>
</ul>
<h2 id="3-3-建立一个地址空间"><a href="#3-3-建立一个地址空间" class="headerlink" title="3.3 建立一个地址空间"></a>3.3 建立一个地址空间</h2><p>大多数用于操作地址空间和页表的xv6代码都写在<strong>kernel&#x2F;vm.c</strong>中，核心数据结构是<code>pagetable_t</code>(uint64)，一个<code>pagetable_t</code>可以是内核页表，也可以是一个进程页表。</p>
<p>最核心的函数是<code>walk</code>和<code>mappages</code>，前者为虚拟地址找到PTE，后者为新映射装载PTE。名称以<code>kvm</code>开头的函数操作内核页表；以<code>uvm</code>开头的函数操作用户页表；其他函数用于二者。<code>copyout</code>和<code>copyin</code>复制数据到用户虚拟地址或从用户虚拟地址复制数据。</p>
<figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">kvminit</span><span class="params">()</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">	kernel_pagetable = (<span class="keyword">pagetable_t</span>)kalloc();</span><br><span class="line">	<span class="built_in">memset</span>(kernel_pagetable, <span class="number">0</span>, PGSIZE);</span><br><span class="line"></span><br><span class="line">	<span class="comment">// uart registers</span></span><br><span class="line">	kvmmap(UART0, UART0, PGSIZE, PTE_R | PTE_W);</span><br><span class="line"></span><br><span class="line">	<span class="comment">// virtio mmio disk interface</span></span><br><span class="line">	kvmmap(VIRTIO0, VIRTIO0, PGSIZE, PTE_R | PTE_W);</span><br><span class="line"></span><br><span class="line">	<span class="comment">// CLINT</span></span><br><span class="line">	kvmmap(CLINT, CLINT, <span class="number">0x10000</span>, PTE_R | PTE_W);</span><br><span class="line"></span><br><span class="line">	<span class="comment">// PLIC</span></span><br><span class="line">	kvmmap(PLIC, PLIC, <span class="number">0x400000</span>, PTE_R | PTE_W);</span><br><span class="line"></span><br><span class="line">	<span class="comment">// map kernel text executable and read-only.</span></span><br><span class="line">	kvmmap(KERNBASE, KERNBASE, (uint64)etext - KERNBASE, PTE_R | PTE_X);</span><br><span class="line"></span><br><span class="line">	<span class="comment">// map kernel data and the physical RAM we&#x27;ll make use of.</span></span><br><span class="line">	kvmmap((uint64)etext, (uint64)etext, PHYSTOP - (uint64)etext, PTE_R | PTE_W);</span><br><span class="line"></span><br><span class="line">	<span class="comment">// map the trampoline for trap entry/exit to</span></span><br><span class="line">	<span class="comment">// the highest virtual address in the kernel.</span></span><br><span class="line">	kvmmap(TRAMPOLINE, (uint64)trampoline, PGSIZE, PTE_R | PTE_X);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>在最开始<code>main</code>调用<code>kvninit</code> 来创建内核的页表。这个调用发生在xv6使能RISC-V分页之前，所以地址直接引用物理内存。</p>
<p>首先分配一个物理页保存根页表页，之后调用<code>kvmmap</code>装载内核需要的转换，</p>
<p><code>kvmmap</code>：调用<code>mappages</code>，<code>mappages</code>将范围虚拟地址到同等范围物理地址的映射装载到一个页表中。</p>
<figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// add a mapping to the kernel page table.</span></span><br><span class="line"><span class="comment">// only used when booting.</span></span><br><span class="line"><span class="comment">// does not flush TLB or enable paging.</span></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">kvmmap</span><span class="params">(uint64 va, uint64 pa, uint64 sz, <span class="keyword">int</span> perm)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">	<span class="keyword">if</span> (mappages(kernel_pagetable, va, sz, pa, perm) != <span class="number">0</span>)</span><br><span class="line">		panic(<span class="string">&quot;kvmmap&quot;</span>);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p><code>walk</code>：为虚拟地址找到PTE，模仿RISC-V分页硬件。每次从三级页表中读9位，使用上一级的9位虚拟地址来查找下一级页表或最终页面的PTE。</p>
<figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// Return the address of the PTE in page table pagetable</span></span><br><span class="line"><span class="comment">// that corresponds to virtual address va.  If alloc!=0,</span></span><br><span class="line"><span class="comment">// create any required page-table pages.</span></span><br><span class="line"><span class="comment">//</span></span><br><span class="line"><span class="comment">// The risc-v Sv39 scheme has three levels of page-table</span></span><br><span class="line"><span class="comment">// pages. A page-table page contains 512 64-bit PTEs.</span></span><br><span class="line"><span class="comment">// A 64-bit virtual address is split into five fields:</span></span><br><span class="line"><span class="comment">//   39..63 -- must be zero.</span></span><br><span class="line"><span class="comment">//   30..38 -- 9 bits of level-2 index.</span></span><br><span class="line"><span class="comment">//   21..29 -- 9 bits of level-1 index.</span></span><br><span class="line"><span class="comment">//   12..20 -- 9 bits of level-0 index.</span></span><br><span class="line"><span class="comment">//    0..11 -- 12 bits of byte offset within the page.</span></span><br><span class="line"><span class="function"><span class="keyword">pte_t</span> *<span class="title">walk</span><span class="params">(<span class="keyword">pagetable_t</span> pagetable, uint64 va, <span class="keyword">int</span> alloc)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">	<span class="keyword">if</span> (va &gt;= MAXVA)</span><br><span class="line">		panic(<span class="string">&quot;walk&quot;</span>);</span><br><span class="line"></span><br><span class="line">	<span class="keyword">for</span> (<span class="keyword">int</span> level = <span class="number">2</span>; level &gt; <span class="number">0</span>; level--)</span><br><span class="line">	&#123;</span><br><span class="line">		<span class="keyword">pte_t</span> *pte = &amp;pagetable[PX(level, va)];</span><br><span class="line">		<span class="keyword">if</span> (*pte &amp; PTE_V)</span><br><span class="line">		&#123;</span><br><span class="line">			pagetable = (<span class="keyword">pagetable_t</span>)PTE2PA(*pte);</span><br><span class="line">		&#125;</span><br><span class="line">		<span class="keyword">else</span></span><br><span class="line">		&#123;</span><br><span class="line">			<span class="keyword">if</span> (!alloc || (pagetable = (<span class="keyword">pde_t</span> *)kalloc()) == <span class="number">0</span>)</span><br><span class="line">				<span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">			<span class="built_in">memset</span>(pagetable, <span class="number">0</span>, PGSIZE);</span><br><span class="line">			*pte = PA2PTE(pagetable) | PTE_V;</span><br><span class="line">		&#125;</span><br><span class="line">	&#125;</span><br><span class="line">	<span class="keyword">return</span> &amp;pagetable[PX(<span class="number">0</span>, va)];</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p><code>kvninit</code>结束后，<code>main</code>调用<code>kvminithart</code>来安装内核页表。它将根页表页的物理地址写入寄存器<code>satp</code>。之后，CPU将使用内核页表转换地址。由于内核使用标识映射，下一条指令的当前虚拟地址将映射到正确的物理内存地址。</p>
<figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// Switch h/w page table register to the kernel&#x27;s page table,</span></span><br><span class="line"><span class="comment">// and enable paging.</span></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">kvminithart</span><span class="params">()</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">	w_satp(MAKE_SATP(kernel_pagetable));</span><br><span class="line">	sfence_vma();<span class="comment">// flush the TLB.</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>之后<code>main</code>调用<code>procinit</code>，为每个进程分配一个内核栈，它将每个栈映射到<code>KSTACK</code>生成的虚拟地址。</p>
<figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// initialize the proc table at boot time.</span></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">procinit</span><span class="params">(<span class="keyword">void</span>)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">	<span class="class"><span class="keyword">struct</span> <span class="title">proc</span> *<span class="title">p</span>;</span></span><br><span class="line"></span><br><span class="line">	initlock(&amp;pid_lock, <span class="string">&quot;nextpid&quot;</span>);</span><br><span class="line">	<span class="keyword">for</span> (p = proc; p &lt; &amp;proc[NPROC]; p++)</span><br><span class="line">	&#123;</span><br><span class="line">		initlock(&amp;p-&gt;lock, <span class="string">&quot;proc&quot;</span>);</span><br><span class="line"></span><br><span class="line">		<span class="comment">// Allocate a page for the process&#x27;s kernel stack.</span></span><br><span class="line">		<span class="comment">// Map it high in memory, followed by an invalid</span></span><br><span class="line">		<span class="comment">// guard page.</span></span><br><span class="line">		<span class="keyword">char</span> *pa = kalloc();</span><br><span class="line">		<span class="keyword">if</span> (pa == <span class="number">0</span>)</span><br><span class="line">			panic(<span class="string">&quot;kalloc&quot;</span>);</span><br><span class="line">		uint64 va = KSTACK((<span class="keyword">int</span>)(p - proc));</span><br><span class="line">		kvmmap(va, (uint64)pa, PGSIZE, PTE_R | PTE_W);</span><br><span class="line">		p-&gt;kstack = va;</span><br><span class="line">	&#125;</span><br><span class="line">	kvminithart();</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>每个RISC-V CPU都将页表条目缓存在转译后备缓冲器（快表&#x2F;TLB）中，RISC-V有一个指令<code>sfence.vma</code>，用于刷新当前CPU的TLB</p>
<h2 id="3-4-物理内存分配"><a href="#3-4-物理内存分配" class="headerlink" title="3.4 物理内存分配"></a>3.4 物理内存分配</h2><p><code>kalloc</code>代码位于<code>kernelkalloc.c</code>，<code>kalloc</code>的数据结构是可供分配的物理内存页的空闲列表，空闲列表受到自旋锁（spin lock）的保护</p>
<figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// Allocate one 4096-byte page of physical memory.</span></span><br><span class="line"><span class="comment">// Returns a pointer that the kernel can use.</span></span><br><span class="line"><span class="comment">// Returns 0 if the memory cannot be allocated.</span></span><br><span class="line"><span class="function"><span class="keyword">void</span> *</span></span><br><span class="line"><span class="function"><span class="title">kalloc</span><span class="params">(<span class="keyword">void</span>)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">	<span class="class"><span class="keyword">struct</span> <span class="title">run</span> *<span class="title">r</span>;</span></span><br><span class="line"></span><br><span class="line">	acquire(&amp;kmem.lock);</span><br><span class="line">	r = kmem.freelist;</span><br><span class="line">	<span class="keyword">if</span> (r)</span><br><span class="line">		kmem.freelist = r-&gt;next;</span><br><span class="line">	release(&amp;kmem.lock);</span><br><span class="line"></span><br><span class="line">	<span class="keyword">if</span> (r)</span><br><span class="line">		<span class="built_in">memset</span>((<span class="keyword">char</span> *)r, <span class="number">5</span>, PGSIZE); <span class="comment">// fill with junk</span></span><br><span class="line">	<span class="keyword">return</span> (<span class="keyword">void</span> *)r;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p><code>main</code>调用<code>kinit</code>来初始化分配器，<code>kinit</code>初始化空闲列表以保存从内核结束到<code>PHYSTOP</code>之间的每一页。</p>
<figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">kinit</span><span class="params">()</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">	initlock(&amp;kmem.lock, <span class="string">&quot;kmem&quot;</span>);</span><br><span class="line">	freerange(end, (<span class="keyword">void</span> *)PHYSTOP);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">freerange</span><span class="params">(<span class="keyword">void</span> *pa_start, <span class="keyword">void</span> *pa_end)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">	<span class="keyword">char</span> *p;</span><br><span class="line">	p = (<span class="keyword">char</span> *)PGROUNDUP((uint64)pa_start);</span><br><span class="line">	<span class="keyword">for</span> (; p + PGSIZE &lt;= (<span class="keyword">char</span> *)pa_end; p += PGSIZE)</span><br><span class="line">		kfree(p);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// Free the page of physical memory pointed at by v,</span></span><br><span class="line"><span class="comment">// which normally should have been returned by a</span></span><br><span class="line"><span class="comment">// call to kalloc().  (The exception is when</span></span><br><span class="line"><span class="comment">// initializing the allocator; see kinit above.)</span></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">kfree</span><span class="params">(<span class="keyword">void</span> *pa)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">	<span class="class"><span class="keyword">struct</span> <span class="title">run</span> *<span class="title">r</span>;</span></span><br><span class="line"></span><br><span class="line">	<span class="keyword">if</span> (((uint64)pa % PGSIZE) != <span class="number">0</span> || (<span class="keyword">char</span> *)pa &lt; end || (uint64)pa &gt;= PHYSTOP)</span><br><span class="line">		panic(<span class="string">&quot;kfree&quot;</span>);</span><br><span class="line"></span><br><span class="line">	<span class="comment">// Fill with junk to catch dangling refs.</span></span><br><span class="line">	<span class="built_in">memset</span>(pa, <span class="number">1</span>, PGSIZE);</span><br><span class="line"></span><br><span class="line">	r = (struct run *)pa;</span><br><span class="line"></span><br><span class="line">	acquire(&amp;kmem.lock);</span><br><span class="line">	r-&gt;next = kmem.freelist;</span><br><span class="line">	kmem.freelist = r;</span><br><span class="line">	release(&amp;kmem.lock);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>



<h2 id="3-5-进程地址空间"><a href="#3-5-进程地址空间" class="headerlink" title="3.5 进程地址空间"></a>3.5 进程地址空间</h2><p>每个进程都有一个单独的页表，当xv6在进程之间切换时，也会更改页表。一个进程的用户内存从虚拟地址零开始，可以增长到MAXVA，原则上允许一个进程内存寻址空间为256G。</p>
<figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// one beyond the highest possible virtual address.</span></span><br><span class="line"><span class="comment">// MAXVA is actually one bit less than the max allowed by</span></span><br><span class="line"><span class="comment">// Sv39, to avoid having to sign-extend virtual addresses</span></span><br><span class="line"><span class="comment">// that have the high bit set.</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">define</span> MAXVA (1L &lt;&lt; (9 + 9 + 9 + 12 - 1))</span></span><br></pre></td></tr></table></figure>

<p><img src="/2022/07/12/XV6/image-20220713121236349.png"></p>
<p>当进程向xv6请求更多的用户内存时，首先使用<code>kalloc</code>来分配物理页面。</p>
<p>内核在用户地址空间的顶部映射一个带有蹦床（trampoline）代码的页面，这样在所有地址空间都可以看到一个单独的物理内存页面。</p>
<p>为了检测用户栈是否溢出了所分配栈内存，xv6在栈正下方放置了一个无效的保护页（guard page）。如果用户栈溢出并且进程试图使用栈下方的地址，那么由于映射无效（<code>PTE_V</code>为0）硬件将生成一个页面故障异常。</p>
<h2 id="3-6-sbrk"><a href="#3-6-sbrk" class="headerlink" title="3.6 sbrk"></a>3.6 sbrk</h2><p><code>sbrk</code>是一个用于进程减少或增长其内存的系统调用。其实是调用<code>growproc</code>实现</p>
<figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="function">uint64</span></span><br><span class="line"><span class="function"><span class="title">sys_sbrk</span><span class="params">(<span class="keyword">void</span>)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">  <span class="keyword">int</span> addr;</span><br><span class="line">  <span class="keyword">int</span> n;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">if</span>(argint(<span class="number">0</span>, &amp;n) &lt; <span class="number">0</span>)</span><br><span class="line">    <span class="keyword">return</span> <span class="number">-1</span>;</span><br><span class="line">  addr = myproc()-&gt;sz;</span><br><span class="line">  <span class="keyword">if</span>(growproc(n) &lt; <span class="number">0</span>)</span><br><span class="line">    <span class="keyword">return</span> <span class="number">-1</span>;</span><br><span class="line">  <span class="keyword">return</span> addr;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p><code>growproc</code>根据<code>n</code>是正的还是负的调用<code>uvmalloc</code>或<code>uvmdealloc</code>。<code>uvmalloc</code>用<code>kalloc</code>分配物理内存，并用<code>mappages</code>将PTE添加到用户页表中；<code>uvmdealloc</code>调用<code>uvmunmap</code>，<code>uvmunmap</code>使用<code>walk</code>来查找对应的PTE，并使用<code>kfree</code>来释放PTE引用的物理内存。</p>
<figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// Grow or shrink user memory by n bytes.</span></span><br><span class="line"><span class="comment">// Return 0 on success, -1 on failure.</span></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">growproc</span><span class="params">(<span class="keyword">int</span> n)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">	uint sz;</span><br><span class="line">	<span class="class"><span class="keyword">struct</span> <span class="title">proc</span> *<span class="title">p</span> =</span> myproc();</span><br><span class="line"></span><br><span class="line">	sz = p-&gt;sz;</span><br><span class="line">	<span class="keyword">if</span> (n &gt; <span class="number">0</span>)</span><br><span class="line">	&#123;</span><br><span class="line">		<span class="keyword">if</span> ((sz = uvmalloc(p-&gt;pagetable, sz, sz + n)) == <span class="number">0</span>)</span><br><span class="line">		&#123;</span><br><span class="line">			<span class="keyword">return</span> <span class="number">-1</span>;</span><br><span class="line">		&#125;</span><br><span class="line">	&#125;</span><br><span class="line">	<span class="keyword">else</span> <span class="keyword">if</span> (n &lt; <span class="number">0</span>)</span><br><span class="line">	&#123;</span><br><span class="line">		sz = uvmdealloc(p-&gt;pagetable, sz, sz + n);</span><br><span class="line">	&#125;</span><br><span class="line">	p-&gt;sz = sz;</span><br><span class="line">	<span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>



<h2 id="3-7-exec"><a href="#3-7-exec" class="headerlink" title="3.7 exec"></a>3.7 exec</h2><p>代码在<code>kernel/exec.c</code>中 </p>
<figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br><span class="line">119</span><br><span class="line">120</span><br><span class="line">121</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">exec</span><span class="params">(<span class="keyword">char</span> *path, <span class="keyword">char</span> **argv)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">	<span class="keyword">char</span> *s, *last;</span><br><span class="line">	<span class="keyword">int</span> i, off;</span><br><span class="line">	uint64 argc, sz = <span class="number">0</span>, sp, ustack[MAXARG + <span class="number">1</span>], stackbase;</span><br><span class="line">	<span class="class"><span class="keyword">struct</span> <span class="title">elfhdr</span> <span class="title">elf</span>;</span></span><br><span class="line">	<span class="class"><span class="keyword">struct</span> <span class="title">inode</span> *<span class="title">ip</span>;</span></span><br><span class="line">	<span class="class"><span class="keyword">struct</span> <span class="title">proghdr</span> <span class="title">ph</span>;</span></span><br><span class="line">	<span class="keyword">pagetable_t</span> pagetable = <span class="number">0</span>, oldpagetable;</span><br><span class="line">	<span class="class"><span class="keyword">struct</span> <span class="title">proc</span> *<span class="title">p</span> =</span> myproc();</span><br><span class="line"></span><br><span class="line">	begin_op();</span><br><span class="line"></span><br><span class="line">	<span class="keyword">if</span> ((ip = namei(path)) == <span class="number">0</span>)</span><br><span class="line">	&#123;</span><br><span class="line">		end_op();</span><br><span class="line">		<span class="keyword">return</span> <span class="number">-1</span>;</span><br><span class="line">	&#125;</span><br><span class="line">	ilock(ip);</span><br><span class="line"></span><br><span class="line">	<span class="comment">// Check ELF header</span></span><br><span class="line">	<span class="keyword">if</span> (readi(ip, <span class="number">0</span>, (uint64)&amp;elf, <span class="number">0</span>, <span class="keyword">sizeof</span>(elf)) != <span class="keyword">sizeof</span>(elf))</span><br><span class="line">		<span class="keyword">goto</span> bad;</span><br><span class="line">	<span class="keyword">if</span> (elf.magic != ELF_MAGIC)</span><br><span class="line">		<span class="keyword">goto</span> bad;</span><br><span class="line"></span><br><span class="line">	<span class="keyword">if</span> ((pagetable = proc_pagetable(p)) == <span class="number">0</span>)</span><br><span class="line">		<span class="keyword">goto</span> bad;</span><br><span class="line"></span><br><span class="line">	<span class="comment">// Load program into memory.</span></span><br><span class="line">	<span class="keyword">for</span> (i = <span class="number">0</span>, off = elf.phoff; i &lt; elf.phnum; i++, off += <span class="keyword">sizeof</span>(ph))</span><br><span class="line">	&#123;</span><br><span class="line">		<span class="keyword">if</span> (readi(ip, <span class="number">0</span>, (uint64)&amp;ph, off, <span class="keyword">sizeof</span>(ph)) != <span class="keyword">sizeof</span>(ph))</span><br><span class="line">			<span class="keyword">goto</span> bad;</span><br><span class="line">		<span class="keyword">if</span> (ph.type != ELF_PROG_LOAD)</span><br><span class="line">			<span class="keyword">continue</span>;</span><br><span class="line">		<span class="keyword">if</span> (ph.memsz &lt; ph.filesz)</span><br><span class="line">			<span class="keyword">goto</span> bad;</span><br><span class="line">		<span class="keyword">if</span> (ph.vaddr + ph.memsz &lt; ph.vaddr)</span><br><span class="line">			<span class="keyword">goto</span> bad;</span><br><span class="line">		uint64 sz1;</span><br><span class="line">		<span class="keyword">if</span> ((sz1 = uvmalloc(pagetable, sz, ph.vaddr + ph.memsz)) == <span class="number">0</span>)</span><br><span class="line">			<span class="keyword">goto</span> bad;</span><br><span class="line">		sz = sz1;</span><br><span class="line">		<span class="keyword">if</span> (ph.vaddr % PGSIZE != <span class="number">0</span>)</span><br><span class="line">			<span class="keyword">goto</span> bad;</span><br><span class="line">		<span class="keyword">if</span> (loadseg(pagetable, ph.vaddr, ip, ph.off, ph.filesz) &lt; <span class="number">0</span>)</span><br><span class="line">			<span class="keyword">goto</span> bad;</span><br><span class="line">	&#125;</span><br><span class="line">	iunlockput(ip);</span><br><span class="line">	end_op();</span><br><span class="line">	ip = <span class="number">0</span>;</span><br><span class="line"></span><br><span class="line">	p = myproc();</span><br><span class="line">	uint64 oldsz = p-&gt;sz;</span><br><span class="line"></span><br><span class="line">	<span class="comment">// Allocate two pages at the next page boundary.</span></span><br><span class="line">	<span class="comment">// Use the second as the user stack.</span></span><br><span class="line">	sz = PGROUNDUP(sz);</span><br><span class="line">	uint64 sz1;</span><br><span class="line">	<span class="keyword">if</span> ((sz1 = uvmalloc(pagetable, sz, sz + <span class="number">2</span> * PGSIZE)) == <span class="number">0</span>)</span><br><span class="line">		<span class="keyword">goto</span> bad;</span><br><span class="line">	sz = sz1;</span><br><span class="line">	uvmclear(pagetable, sz - <span class="number">2</span> * PGSIZE);</span><br><span class="line">	sp = sz;</span><br><span class="line">	stackbase = sp - PGSIZE;</span><br><span class="line"></span><br><span class="line">	<span class="comment">// Push argument strings, prepare rest of stack in ustack.</span></span><br><span class="line">	<span class="keyword">for</span> (argc = <span class="number">0</span>; argv[argc]; argc++)</span><br><span class="line">	&#123;</span><br><span class="line">		<span class="keyword">if</span> (argc &gt;= MAXARG)</span><br><span class="line">			<span class="keyword">goto</span> bad;</span><br><span class="line">		sp -= <span class="built_in">strlen</span>(argv[argc]) + <span class="number">1</span>;</span><br><span class="line">		sp -= sp % <span class="number">16</span>; <span class="comment">// riscv sp must be 16-byte aligned</span></span><br><span class="line">		<span class="keyword">if</span> (sp &lt; stackbase)</span><br><span class="line">			<span class="keyword">goto</span> bad;</span><br><span class="line">		<span class="keyword">if</span> (copyout(pagetable, sp, argv[argc], <span class="built_in">strlen</span>(argv[argc]) + <span class="number">1</span>) &lt; <span class="number">0</span>)</span><br><span class="line">			<span class="keyword">goto</span> bad;</span><br><span class="line">		ustack[argc] = sp;</span><br><span class="line">	&#125;</span><br><span class="line">	ustack[argc] = <span class="number">0</span>;</span><br><span class="line"></span><br><span class="line">	<span class="comment">// push the array of argv[] pointers.</span></span><br><span class="line">	sp -= (argc + <span class="number">1</span>) * <span class="keyword">sizeof</span>(uint64);</span><br><span class="line">	sp -= sp % <span class="number">16</span>;</span><br><span class="line">	<span class="keyword">if</span> (sp &lt; stackbase)</span><br><span class="line">		<span class="keyword">goto</span> bad;</span><br><span class="line">	<span class="keyword">if</span> (copyout(pagetable, sp, (<span class="keyword">char</span> *)ustack, (argc + <span class="number">1</span>) * <span class="keyword">sizeof</span>(uint64)) &lt; <span class="number">0</span>)</span><br><span class="line">		<span class="keyword">goto</span> bad;</span><br><span class="line"></span><br><span class="line">	<span class="comment">// arguments to user main(argc, argv)</span></span><br><span class="line">	<span class="comment">// argc is returned via the system call return</span></span><br><span class="line">	<span class="comment">// value, which goes in a0.</span></span><br><span class="line">	p-&gt;trapframe-&gt;a1 = sp;</span><br><span class="line"></span><br><span class="line">	<span class="comment">// Save program name for debugging.</span></span><br><span class="line">	<span class="keyword">for</span> (last = s = path; *s; s++)</span><br><span class="line">		<span class="keyword">if</span> (*s == <span class="string">&#x27;/&#x27;</span>)</span><br><span class="line">			last = s + <span class="number">1</span>;</span><br><span class="line">	safestrcpy(p-&gt;name, last, <span class="keyword">sizeof</span>(p-&gt;name));</span><br><span class="line"></span><br><span class="line">	<span class="comment">// Commit to the user image.</span></span><br><span class="line">	oldpagetable = p-&gt;pagetable;</span><br><span class="line">	p-&gt;pagetable = pagetable;</span><br><span class="line">	p-&gt;sz = sz;</span><br><span class="line">	p-&gt;trapframe-&gt;epc = elf.entry; <span class="comment">// initial program counter = main</span></span><br><span class="line">	p-&gt;trapframe-&gt;sp = sp;		   <span class="comment">// initial stack pointer</span></span><br><span class="line">	proc_freepagetable(oldpagetable, oldsz);</span><br><span class="line"></span><br><span class="line">	<span class="keyword">return</span> argc; <span class="comment">// this ends up in a0, the first argument to main(argc, argv)</span></span><br><span class="line"></span><br><span class="line">bad:</span><br><span class="line">	<span class="keyword">if</span> (pagetable)</span><br><span class="line">		proc_freepagetable(pagetable, sz);</span><br><span class="line">	<span class="keyword">if</span> (ip)</span><br><span class="line">	&#123;</span><br><span class="line">		iunlockput(ip);</span><br><span class="line">		end_op();</span><br><span class="line">	&#125;</span><br><span class="line">	<span class="keyword">return</span> <span class="number">-1</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>首先使用<code>namei</code>打开指定二进制path，之后读ELF的文件头，检查magic。</p>
<p>之后使用<code>proc_pagetable</code>分配一个没有用户映射的新页表，使用<code>uvmalloc</code> 为每个ELF段分配内存，并使用<code>loadseg</code> 将每个段加载到内存中。<code>loadseg</code>使用<code>walkaddr</code>找到分配内存的物理地址，在该地址写入ELF段的每一页，并使用<code>readi</code>从文件中读取。</p>
<p>现在<code>exec</code>分配并初始化用户栈。它只分配一个栈页面。<code>exec</code>一次将参数中的一个字符串复制到栈顶，并在<code>ustack</code>中记录指向它们的指针。它在传递给<code>main</code>的<code>argv</code>列表的末尾放置一个空指针。<code>ustack</code>中的前三个条目是伪返回程序计数器（fake return program counter）、<code>argc</code>和<code>argv</code>指针。</p>
<p><code>exec</code>在栈页面的正下方放置了一个不可访问的页面，这样试图使用超过一个页面的程序就会出错。</p>
<p>在准备新内存映像的过程中，如果<code>exec</code>检测到像无效程序段这样的错误，它会跳到标签<code>bad</code>，释放新映像，并返回-1。<code>exec</code>必须等待系统调用会成功后再释放旧映像：因为如果旧映像消失了，系统调用将无法返回-1。<code>exec</code>中唯一的错误情况发生在映像的创建过程中。一旦映像完成，<code>exec</code>就可以提交到新的页表(上述代码104行)并释放旧的页表(上述代码108行)。</p>
<h2 id="3-8-Real-world"><a href="#3-8-Real-world" class="headerlink" title="3.8 Real world"></a>3.8 Real world</h2><p>RISC-V支持物理地址级别的保护，但xv6没有使用这个特性。</p>
<p>xv6内核缺少一个类似<code>malloc</code>可以为小对象提供内存的分配器，这使得内核无法使用需要动态分配的复杂数据结构。</p>
<h1 id="Chapter-4-Traps-and-system-calls"><a href="#Chapter-4-Traps-and-system-calls" class="headerlink" title="Chapter 4:Traps and system calls"></a>Chapter 4:Traps and system calls</h1><p>有三种事件会导致中央处理器搁置普通指令的执行，并强制将控制权转移到处理该事件的特殊代码上：陷阱（trap）</p>
<ol>
<li>系统调用：当用户程序执行<code>ecall</code>指令要求内核为其做些什么时</li>
<li>异常：（用户或内核）指令做了一些非法的事情，例如除以零或使用无效的虚拟地址</li>
<li>设备中断：例如当磁盘硬件完成读或写请求时，向系统表明它需要被关注</li>
</ol>
<p>通常，陷阱发生时正在执行的任何代码都需要稍后恢复。</p>
<p>通常的顺序是陷阱强制将控制权转移到内核；内核保存寄存器和其他状态，以便可以恢复执行；内核执行适当的处理程序代码（例如，系统调用接口或设备驱动程序）；内核恢复保存的状态并从陷阱中返回；原始代码从它停止的地方恢复。</p>
<h2 id="4-1-RISC-V陷入机制"><a href="#4-1-RISC-V陷入机制" class="headerlink" title="4.1 RISC-V陷入机制"></a>4.1 RISC-V陷入机制</h2><p>每个RISC-V CPU都有一组控制寄存器，内核通过向这些寄存器写入内容来告诉CPU如何处理陷阱，内核可以读取这些寄存器来明确已经发生的陷阱。</p>
<p>一些重要的寄存器介绍：</p>
<ul>
<li><code>stvec</code>：内核在这里写入其陷阱处理程序的地址；RISC-V跳转到这里处理陷阱。</li>
<li><code>sepc</code>：当发生陷阱时，RISC-V会在这里保存程序计数器<code>pc</code>（因为<code>pc</code>会被<code>stvec</code>覆盖）。<code>sret</code>（从陷阱返回）指令会将<code>sepc</code>复制到<code>pc</code>。内核可以写入<code>sepc</code>来控制<code>sret</code>的去向。</li>
<li><code>scause</code>： RISC-V在这里放置一个描述陷阱原因的数字。</li>
<li><code>sscratch</code>：内核在这里放置了一个值，这个值在陷阱处理程序一开始就会派上用场。</li>
<li><code>sstatus</code>：其中的<strong>SIE</strong>位控制设备中断是否启用。如果内核清空<strong>SIE</strong>，RISC-V将推迟设备中断，直到内核重新设置<strong>SIE</strong>。<strong>SPP</strong>位指示陷阱是来自用户模式还是管理模式，并控制<code>sret</code>返回的模式。</li>
</ul>
<p>上述寄存器在用户模式下不能读取和写入。</p>
<p>当需要强制执行陷阱时，RISC-V硬件对所有陷阱类型（计时器中断除外）执行以下操作：</p>
<ol>
<li>如果陷阱是设备中断，并且状态<strong>SIE</strong>位被清空，则不执行以下任何操作。</li>
<li>清除<strong>SIE</strong>以禁用中断。</li>
<li>将<code>pc</code>复制到<code>sepc</code>。</li>
<li>将当前模式（用户或管理）保存在状态的<strong>SPP</strong>位中。</li>
<li>设置<code>scause</code>以反映产生陷阱的原因。</li>
<li>将模式设置为管理模式。</li>
<li>将<code>stvec</code>复制到<code>pc</code>。</li>
<li>在新的<code>pc</code>上开始执行。</li>
</ol>
<blockquote>
<p>   <strong>CPU不会切换到内核页表，不会切换到内核栈，也不会保存除<code>pc</code>之外的任何寄存器。</strong></p>
</blockquote>
<h2 id="4-2-从用户空间陷入"><a href="#4-2-从用户空间陷入" class="headerlink" title="4.2 从用户空间陷入"></a>4.2 从用户空间陷入</h2><blockquote>
<p>  来自用户空间的陷阱的高级路径是<code>uservec</code> (<strong>kernel&#x2F;trampoline.S</strong>)，然后是<code>usertrap</code> (<strong>kernel&#x2F;trap.c</strong>)；返回时，先是<code>usertrapret</code> (<strong>kernel&#x2F;trap.c</strong>)，然后是<code>userret</code> (<strong>kernel&#x2F;trampoline.S</strong>)。</p>
<p>  由于RISC-V硬件在陷阱期间不会切换页表，所以用户页表必须包括<code>uservec</code>（<strong>stvec</strong>指向的陷阱向量指令）的映射。<code>uservec</code>必须切换<code>satp</code>以指向内核页表；为了在切换后继续执行指令，<code>uservec</code>必须在内核页表中与用户页表中映射相同的地址。</p>
<p>  xv6使用包含<code>uservec</code>的蹦床页面（trampoline page）来满足这些约束。xv6将蹦床页面映射到内核页表和每个用户页表中相同的虚拟地址。这个虚拟地址是<code>TRAMPOLINE</code>。蹦床内容在<strong>trampoline.S</strong>中设置，并且（当执行用户代码时）<code>stvec</code>设置为<code>uservec</code> 。</p>
</blockquote>
<blockquote>
<p>  <a target="_blank" rel="noopener" href="http://xv6.dgs.zone/tranlate_books/book-riscv-rev1/c4/s2.html">http://xv6.dgs.zone/tranlate_books/book-riscv-rev1/c4/s2.html</a></p>
</blockquote>
<p>陷入：</p>
<p><code>uservec</code>：</p>
<figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br></pre></td><td class="code"><pre><span class="line">.globl uservec</span><br><span class="line">uservec:    </span><br><span class="line">        <span class="meta"># trap.c sets stvec to point here, so</span></span><br><span class="line">        <span class="meta"># traps from user space start here,</span></span><br><span class="line">        <span class="meta"># in supervisor mode, but with a</span></span><br><span class="line">        <span class="meta"># user page table.</span></span><br><span class="line">        #</span><br><span class="line">        <span class="meta"># sscratch points to where the process<span class="meta-string">&#x27;s p-&gt;trapframe is</span></span></span><br><span class="line"><span class="meta-string"><span class="meta">        # mapped into user space, at TRAPFRAME.</span></span></span><br><span class="line"><span class="meta-string"><span class="meta">        #</span></span></span><br><span class="line"><span class="meta-string"><span class="meta">        </span></span></span><br><span class="line"><span class="meta-string"><span class="meta">	# swap a0 and sscratch</span></span></span><br><span class="line"><span class="meta-string"><span class="meta">        # so that a0 is TRAPFRAME</span></span></span><br><span class="line"><span class="meta-string"><span class="meta">        csrrw a0, sscratch, a0</span></span></span><br><span class="line"><span class="meta-string"><span class="meta"></span></span></span><br><span class="line"><span class="meta-string"><span class="meta">        # save the user registers in TRAPFRAME</span></span></span><br><span class="line"><span class="meta-string"><span class="meta">        sd ra, 40(a0)</span></span></span><br><span class="line"><span class="meta-string"><span class="meta">        sd sp, 48(a0)</span></span></span><br><span class="line"><span class="meta-string"><span class="meta">        sd gp, 56(a0)</span></span></span><br><span class="line"><span class="meta-string"><span class="meta">        sd tp, 64(a0)</span></span></span><br><span class="line"><span class="meta-string"><span class="meta">        sd t0, 72(a0)</span></span></span><br><span class="line"><span class="meta-string"><span class="meta">        sd t1, 80(a0)</span></span></span><br><span class="line"><span class="meta-string"><span class="meta">        sd t2, 88(a0)</span></span></span><br><span class="line"><span class="meta-string"><span class="meta">        sd s0, 96(a0)</span></span></span><br><span class="line"><span class="meta-string"><span class="meta">        sd s1, 104(a0)</span></span></span><br><span class="line"><span class="meta-string"><span class="meta">        sd a1, 120(a0)</span></span></span><br><span class="line"><span class="meta-string"><span class="meta">        sd a2, 128(a0)</span></span></span><br><span class="line"><span class="meta-string"><span class="meta">        sd a3, 136(a0)</span></span></span><br><span class="line"><span class="meta-string"><span class="meta">        sd a4, 144(a0)</span></span></span><br><span class="line"><span class="meta-string"><span class="meta">        sd a5, 152(a0)</span></span></span><br><span class="line"><span class="meta-string"><span class="meta">        sd a6, 160(a0)</span></span></span><br><span class="line"><span class="meta-string"><span class="meta">        sd a7, 168(a0)</span></span></span><br><span class="line"><span class="meta-string"><span class="meta">        sd s2, 176(a0)</span></span></span><br><span class="line"><span class="meta-string"><span class="meta">        sd s3, 184(a0)</span></span></span><br><span class="line"><span class="meta-string"><span class="meta">        sd s4, 192(a0)</span></span></span><br><span class="line"><span class="meta-string"><span class="meta">        sd s5, 200(a0)</span></span></span><br><span class="line"><span class="meta-string"><span class="meta">        sd s6, 208(a0)</span></span></span><br><span class="line"><span class="meta-string"><span class="meta">        sd s7, 216(a0)</span></span></span><br><span class="line"><span class="meta-string"><span class="meta">        sd s8, 224(a0)</span></span></span><br><span class="line"><span class="meta-string"><span class="meta">        sd s9, 232(a0)</span></span></span><br><span class="line"><span class="meta-string"><span class="meta">        sd s10, 240(a0)</span></span></span><br><span class="line"><span class="meta-string"><span class="meta">        sd s11, 248(a0)</span></span></span><br><span class="line"><span class="meta-string"><span class="meta">        sd t3, 256(a0)</span></span></span><br><span class="line"><span class="meta-string"><span class="meta">        sd t4, 264(a0)</span></span></span><br><span class="line"><span class="meta-string"><span class="meta">        sd t5, 272(a0)</span></span></span><br><span class="line"><span class="meta-string"><span class="meta">        sd t6, 280(a0)</span></span></span><br><span class="line"><span class="meta-string"><span class="meta"></span></span></span><br><span class="line"><span class="meta-string"><span class="meta">	# save the user a0 in p-&gt;trapframe-&gt;a0</span></span></span><br><span class="line"><span class="meta-string"><span class="meta">        csrr t0, sscratch</span></span></span><br><span class="line"><span class="meta-string"><span class="meta">        sd t0, 112(a0)</span></span></span><br><span class="line"><span class="meta-string"><span class="meta"></span></span></span><br><span class="line"><span class="meta-string"><span class="meta">        # restore kernel stack pointer from p-&gt;trapframe-&gt;kernel_sp</span></span></span><br><span class="line"><span class="meta-string"><span class="meta">        ld sp, 8(a0)</span></span></span><br><span class="line"><span class="meta-string"><span class="meta"></span></span></span><br><span class="line"><span class="meta-string"><span class="meta">        # make tp hold the current hartid, from p-&gt;trapframe-&gt;kernel_hartid</span></span></span><br><span class="line"><span class="meta-string"><span class="meta">        ld tp, 32(a0)</span></span></span><br><span class="line"><span class="meta-string"><span class="meta"></span></span></span><br><span class="line"><span class="meta-string"><span class="meta">        # load the address of usertrap(), p-&gt;trapframe-&gt;kernel_trap</span></span></span><br><span class="line"><span class="meta-string"><span class="meta">        ld t0, 16(a0)</span></span></span><br><span class="line"><span class="meta-string"><span class="meta"></span></span></span><br><span class="line"><span class="meta-string"><span class="meta">        # restore kernel page table from p-&gt;trapframe-&gt;kernel_satp</span></span></span><br><span class="line"><span class="meta-string"><span class="meta">        ld t1, 0(a0)</span></span></span><br><span class="line"><span class="meta-string"><span class="meta">        csrw satp, t1</span></span></span><br><span class="line"><span class="meta-string"><span class="meta">        sfence.vma zero, zero</span></span></span><br><span class="line"><span class="meta-string"><span class="meta"></span></span></span><br><span class="line"><span class="meta-string"><span class="meta">        # a0 is no longer valid, since the kernel page</span></span></span><br><span class="line"><span class="meta-string"><span class="meta">        # table does not specially map p-&gt;tf.</span></span></span><br><span class="line"><span class="meta-string"><span class="meta"></span></span></span><br><span class="line"><span class="meta-string"><span class="meta">        # jump to usertrap(), which does not return</span></span></span><br><span class="line"><span class="meta-string"><span class="meta">        jr t0</span></span></span><br><span class="line"><span class="meta-string"><span class="meta"></span></span></span><br></pre></td></tr></table></figure>

<p><code>uservec</code>启动时，所有32个寄存器都包含被中断代码所拥有的值。</p>
<p><code>uservec</code>开始时的<code>csrrw</code>指令交换了<code>a0</code>和<code>sscratch</code>的内容。现在用户代码的<code>a0</code>被保存了；<code>uservec</code>有一个寄存器（<code>a0</code>）可以使用；<code>a0</code>包含内核以前放在<code>sscratch</code>中的值。</p>
<blockquote>
<p>   这块先过，，，有点跟不懂</p>
</blockquote>
<h2 id="4-3-调用系统调用"><a href="#4-3-调用系统调用" class="headerlink" title="4.3 调用系统调用"></a>4.3 调用系统调用</h2><p>第二章的时候<strong>initcode.S</strong>调用<code>exec</code>系统调用后，看看用户调用是如何在内核中实现<code>exec</code>系统调用的。</p>
<p>用户代码将<code>exec</code>需要的参数放在寄存器<code>a0</code>和<code>a1</code>中，并将系统调用号放在<code>a7</code>中。</p>
<p><code>ecall</code>指令陷入(trap)到内核中，执行<code>uservec</code>、<code>usertrap</code>和<code>syscall</code>，和$(4.2)$步骤一样</p>
<figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// kernel/syscall.c</span></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">syscall</span><span class="params">(<span class="keyword">void</span>)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">	<span class="keyword">int</span> num;</span><br><span class="line">	<span class="class"><span class="keyword">struct</span> <span class="title">proc</span> *<span class="title">p</span> =</span> myproc();</span><br><span class="line"></span><br><span class="line">	num = p-&gt;trapframe-&gt;a7;</span><br><span class="line">	<span class="keyword">if</span> (num &gt; <span class="number">0</span> &amp;&amp; num &lt; NELEM(syscalls) &amp;&amp; syscalls[num])</span><br><span class="line">	&#123;</span><br><span class="line">		p-&gt;trapframe-&gt;a0 = syscalls[num]();</span><br><span class="line">	&#125;</span><br><span class="line">	<span class="keyword">else</span></span><br><span class="line">	&#123;</span><br><span class="line">		<span class="built_in">printf</span>(<span class="string">&quot;%d %s: unknown sys call %d\n&quot;</span>,</span><br><span class="line">			   p-&gt;pid, p-&gt;name, num);</span><br><span class="line">		p-&gt;trapframe-&gt;a0 = <span class="number">-1</span>;</span><br><span class="line">	&#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p><code>syscall</code>首先从陷阱帧(trapframe)中保存的a7检索系统调用号，并用它索引到<code>syscalls</code>中，对于第一次系统调用，<code>a7</code>中的内容是<code>SYS_exec</code>（<strong>kernel&#x2F;syscall. h</strong>），导致了对系统调用接口函数<code>sys_exec</code>的调用。</p>
<p>当系统调用接口函数返回时，<code>syscall</code>将其返回值记录在<code>p-&gt;trapframe-&gt;a0</code>中。这将导致原始用户空间对<code>exec()</code>的调用返回该值，因为RISC-V上的C调用约定将返回值放在<code>a0</code>中。系统调用通常返回负数表示错误，返回零或正数表示成功。如果系统调用号无效，<code>syscall</code>打印错误并返回-1。</p>
<h2 id="4-4-系统调用参数"><a href="#4-4-系统调用参数" class="headerlink" title="4.4 系统调用参数"></a>4.4 系统调用参数</h2><p>函数<code>artint</code>、<code>artaddr</code>和<code>artfd</code>从陷阱框架中检索第n个<strong>系统调用参数</strong>并以整数、指针或文件描述符的形式保存。他们都调用<code>argraw</code>来检索相应的保存的用户寄存器（<strong>kernel&#x2F;syscall.c</strong>）。</p>
<figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// Fetch the nth 32-bit system call argument.</span></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">argint</span><span class="params">(<span class="keyword">int</span> n, <span class="keyword">int</span> *ip)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">	*ip = argraw(n);</span><br><span class="line">	<span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// Retrieve an argument as a pointer.</span></span><br><span class="line"><span class="comment">// Doesn&#x27;t check for legality, since</span></span><br><span class="line"><span class="comment">// copyin/copyout will do that.</span></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">argaddr</span><span class="params">(<span class="keyword">int</span> n, uint64 *ip)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">	*ip = argraw(n);</span><br><span class="line">	<span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// Fetch the nth word-sized system call argument as a null-terminated string.</span></span><br><span class="line"><span class="comment">// Copies into buf, at most max.</span></span><br><span class="line"><span class="comment">// Returns string length if OK (including nul), -1 if error.</span></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">argstr</span><span class="params">(<span class="keyword">int</span> n, <span class="keyword">char</span> *buf, <span class="keyword">int</span> max)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">	uint64 addr;</span><br><span class="line">	<span class="keyword">if</span> (argaddr(n, &amp;addr) &lt; <span class="number">0</span>)</span><br><span class="line">		<span class="keyword">return</span> <span class="number">-1</span>;</span><br><span class="line">	<span class="keyword">return</span> fetchstr(addr, buf, max);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">static</span> uint64</span></span><br><span class="line"><span class="function"><span class="title">argraw</span><span class="params">(<span class="keyword">int</span> n)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">	<span class="class"><span class="keyword">struct</span> <span class="title">proc</span> *<span class="title">p</span> =</span> myproc();</span><br><span class="line">	<span class="keyword">switch</span> (n)</span><br><span class="line">	&#123;</span><br><span class="line">	<span class="keyword">case</span> <span class="number">0</span>:</span><br><span class="line">		<span class="keyword">return</span> p-&gt;trapframe-&gt;a0;</span><br><span class="line">	<span class="keyword">case</span> <span class="number">1</span>:</span><br><span class="line">		<span class="keyword">return</span> p-&gt;trapframe-&gt;a1;</span><br><span class="line">	<span class="keyword">case</span> <span class="number">2</span>:</span><br><span class="line">		<span class="keyword">return</span> p-&gt;trapframe-&gt;a2;</span><br><span class="line">	<span class="keyword">case</span> <span class="number">3</span>:</span><br><span class="line">		<span class="keyword">return</span> p-&gt;trapframe-&gt;a3;</span><br><span class="line">	<span class="keyword">case</span> <span class="number">4</span>:</span><br><span class="line">		<span class="keyword">return</span> p-&gt;trapframe-&gt;a4;</span><br><span class="line">	<span class="keyword">case</span> <span class="number">5</span>:</span><br><span class="line">		<span class="keyword">return</span> p-&gt;trapframe-&gt;a5;</span><br><span class="line">	&#125;</span><br><span class="line">	panic(<span class="string">&quot;argraw&quot;</span>);</span><br><span class="line">	<span class="keyword">return</span> <span class="number">-1</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>内核实现了安全地将数据传输到用户提供的地址和从用户提供的地址传输数据的功能。<code>fetchstr</code>是一个例子（<strong>kernel&#x2F;syscall.c</strong>）。文件系统调用，如<code>exec</code>，使用<code>fetchstr</code>从用户空间检索字符串文件名参数。<code>fetchstr</code>调用<code>copyinstr</code>来完成。</p>
<figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// Fetch the nul-terminated string at addr from the current process.</span></span><br><span class="line"><span class="comment">// Returns length of string, not including nul, or -1 for error.</span></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">fetchstr</span><span class="params">(uint64 addr, <span class="keyword">char</span> *buf, <span class="keyword">int</span> max)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">	<span class="class"><span class="keyword">struct</span> <span class="title">proc</span> *<span class="title">p</span> =</span> myproc();</span><br><span class="line">	<span class="keyword">int</span> err = copyinstr(p-&gt;pagetable, buf, addr, max);</span><br><span class="line">	<span class="keyword">if</span> (err &lt; <span class="number">0</span>)</span><br><span class="line">		<span class="keyword">return</span> err;</span><br><span class="line">	<span class="keyword">return</span> <span class="built_in">strlen</span>(buf);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p><code>copyinstr</code>（<strong>kernel&#x2F;vm.c</strong>）从用户页表页表中的虚拟地址<code>srcva</code>复制<code>max</code>字节到<code>dst</code>。又调用<code>walk</code>遍历页表，以确定<code>srcva</code>的物理地址<code>pa0</code>。由于内核将所有物理RAM地址映射到同一个内核虚拟地址，<code>copyinstr</code>可以直接将字符串字节从<code>pa0</code>复制到<code>dst</code>。<code>walkaddr</code>（<strong>kernel&#x2F;vm.c</strong>）检查用户提供的虚拟地址是否为进程用户地址空间的一部分，因此程序不能欺骗内核读取其他内存。一个类似的函数<code>copyout</code>，将数据从内核复制到用户提供的地址。</p>
<figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// Copy a null-terminated string from user to kernel.</span></span><br><span class="line"><span class="comment">// Copy bytes to dst from virtual address srcva in a given page table,</span></span><br><span class="line"><span class="comment">// until a &#x27;\0&#x27;, or max.</span></span><br><span class="line"><span class="comment">// Return 0 on success, -1 on error.</span></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">copyinstr</span><span class="params">(<span class="keyword">pagetable_t</span> pagetable, <span class="keyword">char</span> *dst, uint64 srcva, uint64 max)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">	uint64 n, va0, pa0;</span><br><span class="line">	<span class="keyword">int</span> got_null = <span class="number">0</span>;</span><br><span class="line"></span><br><span class="line">	<span class="keyword">while</span> (got_null == <span class="number">0</span> &amp;&amp; max &gt; <span class="number">0</span>)</span><br><span class="line">	&#123;</span><br><span class="line">		va0 = PGROUNDDOWN(srcva);</span><br><span class="line">		pa0 = walkaddr(pagetable, va0);</span><br><span class="line">		<span class="keyword">if</span> (pa0 == <span class="number">0</span>)</span><br><span class="line">			<span class="keyword">return</span> <span class="number">-1</span>;</span><br><span class="line">		n = PGSIZE - (srcva - va0);</span><br><span class="line">		<span class="keyword">if</span> (n &gt; max)</span><br><span class="line">			n = max;</span><br><span class="line"></span><br><span class="line">		<span class="keyword">char</span> *p = (<span class="keyword">char</span> *)(pa0 + (srcva - va0));</span><br><span class="line">		<span class="keyword">while</span> (n &gt; <span class="number">0</span>)</span><br><span class="line">		&#123;</span><br><span class="line">			<span class="keyword">if</span> (*p == <span class="string">&#x27;\0&#x27;</span>)</span><br><span class="line">			&#123;</span><br><span class="line">				*dst = <span class="string">&#x27;\0&#x27;</span>;</span><br><span class="line">				got_null = <span class="number">1</span>;</span><br><span class="line">				<span class="keyword">break</span>;</span><br><span class="line">			&#125;</span><br><span class="line">			<span class="keyword">else</span></span><br><span class="line">			&#123;</span><br><span class="line">				*dst = *p;</span><br><span class="line">			&#125;</span><br><span class="line">			--n;</span><br><span class="line">			--max;</span><br><span class="line">			p++;</span><br><span class="line">			dst++;</span><br><span class="line">		&#125;</span><br><span class="line"></span><br><span class="line">		srcva = va0 + PGSIZE;</span><br><span class="line">	&#125;</span><br><span class="line">	<span class="keyword">if</span> (got_null)</span><br><span class="line">	&#123;</span><br><span class="line">		<span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">	&#125;</span><br><span class="line">	<span class="keyword">else</span></span><br><span class="line">	&#123;</span><br><span class="line">		<span class="keyword">return</span> <span class="number">-1</span>;</span><br><span class="line">	&#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h2 id="4-5-从内核空间陷入"><a href="#4-5-从内核空间陷入" class="headerlink" title="4.5 从内核空间陷入"></a>4.5 从内核空间陷入</h2><p>xv6根据执行的是用户代码还是内核代码，对CPU陷阱寄存器的配置有所不同。当在CPU上执行内核时，内核将<code>stvec</code>指向<code>kernelvec</code>(<strong>kernel&#x2F;kernelvec.S</strong>)的汇编代码。由于xv6已经在内核中，<code>kernelvec</code>可以依赖于设置为内核页表的<code>satp</code>，以及指向有效内核栈的栈指针。</p>
<p><code>Kernelvec</code>在保存寄存器后跳转到<code>kerneltrap</code>(<strong>kernel&#x2F;trap.c</strong>)。为两种类型的陷阱做好了准备：设备中断和异常。调用<code>devintr</code>(<strong>kernel&#x2F;trap.c</strong>)来检查和处理中断。如果陷阱不是设备中断，则必定是一个异常，内核中的异常将是一个致命的错误；内核调用<code>panic</code>并停止执行。</p>
<p>如果由于计时器中断而调用了<code>kerneltrap</code>，并且一个进程的内核线程正在运行（而不是调度程序线程），<code>kerneltrap</code>会调用<code>yield</code>，给其他线程一个运行的机会。</p>
<figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// interrupts and exceptions from kernel code go here via kernelvec,</span></span><br><span class="line"><span class="comment">// on whatever the current kernel stack is.</span></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">kerneltrap</span><span class="params">()</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">	<span class="keyword">int</span> which_dev = <span class="number">0</span>;</span><br><span class="line">	uint64 sepc = r_sepc();</span><br><span class="line">	uint64 sstatus = r_sstatus();</span><br><span class="line">	uint64 scause = r_scause();</span><br><span class="line"></span><br><span class="line">	<span class="keyword">if</span> ((sstatus &amp; SSTATUS_SPP) == <span class="number">0</span>)</span><br><span class="line">		panic(<span class="string">&quot;kerneltrap: not from supervisor mode&quot;</span>);</span><br><span class="line">	<span class="keyword">if</span> (intr_get() != <span class="number">0</span>)</span><br><span class="line">		panic(<span class="string">&quot;kerneltrap: interrupts enabled&quot;</span>);</span><br><span class="line"></span><br><span class="line">	<span class="keyword">if</span> ((which_dev = devintr()) == <span class="number">0</span>)</span><br><span class="line">	&#123;</span><br><span class="line">		<span class="built_in">printf</span>(<span class="string">&quot;scause %p\n&quot;</span>, scause);</span><br><span class="line">		<span class="built_in">printf</span>(<span class="string">&quot;sepc=%p stval=%p\n&quot;</span>, r_sepc(), r_stval());</span><br><span class="line">		panic(<span class="string">&quot;kerneltrap&quot;</span>);</span><br><span class="line">	&#125;</span><br><span class="line"></span><br><span class="line">	<span class="comment">// give up the CPU if this is a timer interrupt.</span></span><br><span class="line">	<span class="keyword">if</span> (which_dev == <span class="number">2</span> &amp;&amp; myproc() != <span class="number">0</span> &amp;&amp; myproc()-&gt;state == RUNNING)</span><br><span class="line">		yield();</span><br><span class="line"></span><br><span class="line">	<span class="comment">// the yield() may have caused some traps to occur,</span></span><br><span class="line">	<span class="comment">// so restore trap registers for use by kernelvec.S&#x27;s sepc instruction.</span></span><br><span class="line">	w_sepc(sepc);</span><br><span class="line">	</span><br></pre></td></tr></table></figure>

<p><code>kerneltrap</code>完成后，返回到任何被陷阱中断的代码。恢复这些控制寄存器并返回到<code>kernelvec</code>(<strong>kernel&#x2F;kernelvec.S</strong>)，之后<code>sret</code>，将<code>sepc</code>复制到<code>pc</code>并恢复中断的内核代码。</p>
<h1 id="Chapter-5-Interrupts-and-device-drivers"><a href="#Chapter-5-Interrupts-and-device-drivers" class="headerlink" title="Chapter 5:Interrupts and device drivers"></a>Chapter 5:Interrupts and device drivers</h1>
                                                                    </div>
                                                                    
                                                                        <div class="prev-or-next">
                                                                            <div class="post-foot-next">
                                                                                
                                                                                    <a href="/2022/07/10/2022-7-10-oslab/" target="_self">
                                                                                        <i class="iconfont icon-chevronleft"></i>
                                                                                        <span>Prev</span>
                                                                                    </a>
                                                                                    
                                                                            </div>
                                                                            <div class="post-attach">
                                                                                <!-- <span class="post-pubtime">
              <i class="iconfont icon-updatetime" title="Update time"></i>
              2022-07-12
            </span> -->

                                                                                
                                                                                            <span class="post-categories">
          <!-- <i class="iconfont icon-bookmark" title="Categories"></i> -->
          
          <!-- <span class="span--category">
            <a href="/categories/Technology/" title="Technology">
              <b>#</b> Technology
            </a>
          </span> -->
                                                                                            
                                                                                                </span>
                                                                                                
                                                                                    <span class="post-tags">
          <!-- <i class="iconfont icon-tags" title="Tags"></i> -->
          
          <!-- <span class="span--tag">
            <a href="/tags/OS/" title="OS">
              <b>#</b> OS
            </a>
          </span> -->
                                                                                    
                                                                                        </span>
                                                                                        
                                                                            </div>
                                                                            <div class="post-foot-prev">
                                                                                
                                                                                    <a href="/2022/07/12/2022-7-12/" target="_self">
                                                                                        <span>Next</span>
                                                                                        <i class="iconfont icon-chevronright"></i>
                                                                                    </a>
                                                                                    
                                                                            </div>
                                                                        </div>
                                                                        
                                                                </div>
                                                                
  <div id="btn-catalog" class="btn-catalog">
    <i class="iconfont icon-catalog"></i>
  </div>
  <div class="post-catalog hidden" id="catalog">
    <div class="title">Contents</div>
    <div class="catalog-content">
      
        <ol class="toc"><li class="toc-item toc-level-1"><a class="toc-link" href="#Chapter-1-Operating-system-interfaces"><span class="toc-text">Chapter 1:Operating system interfaces</span></a><ol class="toc-child"><li class="toc-item toc-level-2"><a class="toc-link" href="#1-1-%E8%BF%9B%E7%A8%8B%E5%92%8C%E5%86%85%E5%AD%98"><span class="toc-text">1.1 进程和内存</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#1-2-I-x2F-O-%E5%92%8C%E6%96%87%E4%BB%B6%E6%8F%8F%E8%BF%B0%E7%AC%A6"><span class="toc-text">1.2 I&#x2F;O 和文件描述符</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#1-3-%E7%AE%A1%E9%81%93"><span class="toc-text">1.3 管道</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#1-4-%E6%96%87%E4%BB%B6%E7%B3%BB%E7%BB%9F"><span class="toc-text">1.4 文件系统</span></a></li></ol></li><li class="toc-item toc-level-1"><a class="toc-link" href="#Chapter-2-Operating-system-organization"><span class="toc-text">Chapter 2:Operating system organization</span></a><ol class="toc-child"><li class="toc-item toc-level-2"><a class="toc-link" href="#2-1-xv6%E6%9E%B6%E6%9E%84"><span class="toc-text">2.1 xv6架构</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#2-2%E8%BF%9B%E7%A8%8B%E6%A6%82%E8%BF%B0"><span class="toc-text">2.2进程概述</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#2-3-%E5%90%AF%E5%8A%A8XV6%E7%9A%84%E7%AC%AC%E4%B8%80%E4%B8%AA%E7%A8%8B%E5%BA%8F"><span class="toc-text">2.3 启动XV6的第一个程序</span></a></li></ol></li><li class="toc-item toc-level-1"><a class="toc-link" href="#Chapter-3-Page-tables"><span class="toc-text">Chapter 3:Page tables</span></a><ol class="toc-child"><li class="toc-item toc-level-2"><a class="toc-link" href="#3-1-%E5%88%86%E9%A1%B5%E7%A1%AC%E4%BB%B6"><span class="toc-text">3.1 分页硬件</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#3-2-%E5%86%85%E6%A0%B8%E5%9C%B0%E5%9D%80%E7%A9%BA%E9%97%B4"><span class="toc-text">3.2 内核地址空间</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#3-3-%E5%BB%BA%E7%AB%8B%E4%B8%80%E4%B8%AA%E5%9C%B0%E5%9D%80%E7%A9%BA%E9%97%B4"><span class="toc-text">3.3 建立一个地址空间</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#3-4-%E7%89%A9%E7%90%86%E5%86%85%E5%AD%98%E5%88%86%E9%85%8D"><span class="toc-text">3.4 物理内存分配</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#3-5-%E8%BF%9B%E7%A8%8B%E5%9C%B0%E5%9D%80%E7%A9%BA%E9%97%B4"><span class="toc-text">3.5 进程地址空间</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#3-6-sbrk"><span class="toc-text">3.6 sbrk</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#3-7-exec"><span class="toc-text">3.7 exec</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#3-8-Real-world"><span class="toc-text">3.8 Real world</span></a></li></ol></li><li class="toc-item toc-level-1"><a class="toc-link" href="#Chapter-4-Traps-and-system-calls"><span class="toc-text">Chapter 4:Traps and system calls</span></a><ol class="toc-child"><li class="toc-item toc-level-2"><a class="toc-link" href="#4-1-RISC-V%E9%99%B7%E5%85%A5%E6%9C%BA%E5%88%B6"><span class="toc-text">4.1 RISC-V陷入机制</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#4-2-%E4%BB%8E%E7%94%A8%E6%88%B7%E7%A9%BA%E9%97%B4%E9%99%B7%E5%85%A5"><span class="toc-text">4.2 从用户空间陷入</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#4-3-%E8%B0%83%E7%94%A8%E7%B3%BB%E7%BB%9F%E8%B0%83%E7%94%A8"><span class="toc-text">4.3 调用系统调用</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#4-4-%E7%B3%BB%E7%BB%9F%E8%B0%83%E7%94%A8%E5%8F%82%E6%95%B0"><span class="toc-text">4.4 系统调用参数</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#4-5-%E4%BB%8E%E5%86%85%E6%A0%B8%E7%A9%BA%E9%97%B4%E9%99%B7%E5%85%A5"><span class="toc-text">4.5 从内核空间陷入</span></a></li></ol></li><li class="toc-item toc-level-1"><a class="toc-link" href="#Chapter-5-Interrupts-and-device-drivers"><span class="toc-text">Chapter 5:Interrupts and device drivers</span></a></li></ol>
      
    </div>
  </div>

  
<script src="/js/catalog.js"></script>




                                                                    
                                                                        <div class="comments-container">
                                                                            







                                                                        </div>
                                                                        
                                                            </div>
                                                            
        
<div class="footer">
  <div class="social">
    <ul>
      
        <li>
          <a title="github" target="_blank" rel="noopener" href="https://github.com/Ghostasky">
            <i class="iconfont icon-github"></i>
          </a>
        </li>
      
        <li>
          <a title="twitter" target="_blank" rel="noopener" href="https://twitter.com/ghostasky">
            <i class="iconfont icon-twitter"></i>
          </a>
        </li>
      
    </ul>
  </div>
  
    
    <div class="footer-more">
      
        <a target="_blank" rel="noopener" href="https://github.com/Ghostasky">怕什么真理无穷，进一寸有进一寸的欢喜。</a>
        
    </div>
  
    
    <div class="footer-more">
      
        <a target="_blank" rel="noopener" href="https://github.com/zchengsite/hexo-theme-oranges">Copyright © 2022 Oranges</a>
        
    </div>
  
    
    <div class="footer-more">
      
        <a target="_blank" rel="noopener" href="https://github.com/zchengsite/hexo-theme-oranges">Theme by Oranges | Powered by Hexo</a>
        
    </div>
  
</div>

      </div>

      <div class="tools-bar">
        <div class="back-to-top tools-bar-item hidden">
  <a href="javascript: void(0)">
    <i class="iconfont icon-chevronup"></i>
  </a>
</div>


<script src="/js/backtotop.js"></script>



        
  <div class="search-icon tools-bar-item" id="search-icon">
    <a href="javascript: void(0)">
      <i class="iconfont icon-search"></i>
    </a>
  </div>

  <div class="search-overlay hidden">
    <div class="search-content" tabindex="0">
      <div class="search-title">
        <span class="search-icon-input">
          <a href="javascript: void(0)">
            <i class="iconfont icon-search"></i>
          </a>
        </span>
        
          <input type="text" class="search-input" id="search-input" placeholder="Search...">
        
        <span class="search-close-icon" id="search-close-icon">
          <a href="javascript: void(0)">
            <i class="iconfont icon-close"></i>
          </a>
        </span>
      </div>
      <div class="search-result" id="search-result"></div>
    </div>
  </div>

  <script type="text/javascript">
    var inputArea = document.querySelector("#search-input")
    var searchOverlayArea = document.querySelector(".search-overlay")

    inputArea.onclick = function() {
      getSearchFile()
      this.onclick = null
    }

    inputArea.onkeydown = function() {
      if(event.keyCode == 13)
        return false
    }

    function openOrHideSearchContent() {
      let isHidden = searchOverlayArea.classList.contains('hidden')
      if (isHidden) {
        searchOverlayArea.classList.remove('hidden')
        document.body.classList.add('hidden')
        // inputArea.focus()
      } else {
        searchOverlayArea.classList.add('hidden')
        document.body.classList.remove('hidden')
      }
    }

    function blurSearchContent(e) {
      if (e.target === searchOverlayArea) {
        openOrHideSearchContent()
      }
    }

    document.querySelector("#search-icon").addEventListener("click", openOrHideSearchContent, false)
    document.querySelector("#search-close-icon").addEventListener("click", openOrHideSearchContent, false)
    searchOverlayArea.addEventListener("click", blurSearchContent, false)

    var searchFunc = function (path, search_id, content_id) {
      'use strict';
      var $input = document.getElementById(search_id);
      var $resultContent = document.getElementById(content_id);
      $resultContent.innerHTML = "<ul><span class='local-search-empty'>First search, index file loading, please wait...<span></ul>";
      $.ajax({
        // 0x01. load xml file
        url: path,
        dataType: "xml",
        success: function (xmlResponse) {
          // 0x02. parse xml file
          var datas = $("entry", xmlResponse).map(function () {
            return {
              title: $("title", this).text(),
              content: $("content", this).text(),
              url: $("url", this).text()
            };
          }).get();
          $resultContent.innerHTML = "";

          $input.addEventListener('input', function () {
            // 0x03. parse query to keywords list
            var str = '<ul class=\"search-result-list\">';
            var keywords = this.value.trim().toLowerCase().split(/[\s\-]+/);
            $resultContent.innerHTML = "";
            if (this.value.trim().length <= 0) {
              return;
            }
            // 0x04. perform local searching
            datas.forEach(function (data) {
              var isMatch = true;
              var content_index = [];
              if (!data.title || data.title.trim() === '') {
                data.title = "Untitled";
              }
              var orig_data_title = data.title.trim();
              var data_title = orig_data_title.toLowerCase();
              var orig_data_content = data.content.trim().replace(/<[^>]+>/g, "");
              var data_content = orig_data_content.toLowerCase();
              var data_url = data.url;
              var index_title = -1;
              var index_content = -1;
              var first_occur = -1;
              // only match artiles with not empty contents
              if (data_content !== '') {
                keywords.forEach(function (keyword, i) {
                  index_title = data_title.indexOf(keyword);
                  index_content = data_content.indexOf(keyword);

                  if (index_title < 0 && index_content < 0) {
                    isMatch = false;
                  } else {
                    if (index_content < 0) {
                      index_content = 0;
                    }
                    if (i == 0) {
                      first_occur = index_content;
                    }
                    // content_index.push({index_content:index_content, keyword_len:keyword_len});
                  }
                });
              } else {
                isMatch = false;
              }
              // 0x05. show search results
              if (isMatch) {
                str += "<li><a href='" + data_url + "' class='search-result-title'>" + orig_data_title + "</a>";
                var content = orig_data_content;
                if (first_occur >= 0) {
                  // cut out 100 characters
                  var start = first_occur - 20;
                  var end = first_occur + 80;

                  if (start < 0) {
                    start = 0;
                  }

                  if (start == 0) {
                    end = 100;
                  }

                  if (end > content.length) {
                    end = content.length;
                  }

                  var match_content = content.substr(start, end);

                  // highlight all keywords
                  keywords.forEach(function (keyword) {
                    var regS = new RegExp(keyword, "gi");
                    match_content = match_content.replace(regS, "<span class=\"search-keyword\">" + keyword + "</span>");
                  });

                  str += "<p class=\"search-result-abstract\">" + match_content + "...</p>"
                }
                str += "</li>";
              }
            });
            str += "</ul>";
            if (str.indexOf('<li>') === -1) {
              return $resultContent.innerHTML = "<ul><span class='local-search-empty'>No result<span></ul>";
            }
            $resultContent.innerHTML = str;
          });
        },
        error: function(xhr, status, error) {
          $resultContent.innerHTML = ""
          if (xhr.status === 404) {
            $resultContent.innerHTML = "<ul><span class='local-search-empty'>The search.xml file was not found, please refer to：<a href='https://github.com/zchengsite/hexo-theme-oranges#configuration' target='_black'>configuration</a><span></ul>";
          } else {
            $resultContent.innerHTML = "<ul><span class='local-search-empty'>The request failed, Try to refresh the page or try again later.<span></ul>";
          }
        }
      });
      $(document).on('click', '#search-close-icon', function() {
        $('#search-input').val('');
        $('#search-result').html('');
      });
    }

    var getSearchFile = function() {
        var path = "/search.xml";
        searchFunc(path, 'search-input', 'search-result');
    }
  </script>




        
  <div class="tools-bar-item theme-icon" id="switch-color-scheme">
    <a href="javascript: void(0)">
      <i id="theme-icon" class="iconfont icon-moon"></i>
    </a>
  </div>

  
<script src="/js/colorscheme.js"></script>





        
  
    <div class="share-icon tools-bar-item">
      <a href="javascript: void(0)" id="share-icon">
        <i class="iconfont iconshare"></i>
      </a>
      <div class="share-content hidden">
        
          <a class="share-item" href="https://twitter.com/intent/tweet?text=' + XV6%E9%98%85%E8%AF%BB%E7%AC%94%E8%AE%B0 + '&url=' + https%3A%2F%2Fghostasky.github.io%2F2022%2F07%2F12%2FXV6%2F + '" target="_blank" title="Twitter">
            <i class="iconfont icon-twitter"></i>
          </a>
        
        
          <a class="share-item" href="https://www.facebook.com/sharer.php?u=https://ghostasky.github.io/2022/07/12/XV6/" target="_blank" title="Facebook">
            <i class="iconfont icon-facebooksquare"></i>
          </a>
        
      </div>
    </div>
  
  
<script src="/js/shares.js"></script>



      </div>
    </div>
  </body>
</html>
